blob: 2482b93241f222f8c38cb59eba73b2a513f9a31d [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Apple Inc.
* All rights reserved.
* Copyright (C) 2013 Adobe Systems Incorporated. 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 "core/layout/LayoutBox.h"
#include <math.h>
#include <algorithm>
#include "core/dom/Document.h"
#include "core/editing/EditingUtilities.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameClient.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLElement.h"
#include "core/html/HTMLFrameElementBase.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/input/EventHandler.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutDeprecatedFlexibleBox.h"
#include "core/layout/LayoutEmbeddedContent.h"
#include "core/layout/LayoutFieldset.h"
#include "core/layout/LayoutFlexibleBox.h"
#include "core/layout/LayoutGrid.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutListMarker.h"
#include "core/layout/LayoutMultiColumnFlowThread.h"
#include "core/layout/LayoutMultiColumnSpannerPlaceholder.h"
#include "core/layout/LayoutTableCell.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LineLayoutBlockFlow.h"
#include "core/layout/api/LineLayoutBox.h"
#include "core/layout/ng/geometry/ng_box_strut.h"
#include "core/layout/ng/ng_fragmentation_utils.h"
#include "core/layout/shapes/ShapeOutsideInfo.h"
#include "core/page/AutoscrollController.h"
#include "core/page/Page.h"
#include "core/page/scrolling/RootScrollerController.h"
#include "core/page/scrolling/RootScrollerUtil.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/page/scrolling/SnapCoordinator.h"
#include "core/paint/BackgroundImageGeometry.h"
#include "core/paint/BoxPaintInvalidator.h"
#include "core/paint/BoxPainter.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/compositing/PaintLayerCompositor.h"
#include "core/style/ShadowList.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/DoubleRect.h"
#include "platform/geometry/FloatQuad.h"
#include "platform/geometry/FloatRoundedRect.h"
#include "platform/wtf/PtrUtil.h"
#include "public/platform/WebRect.h"
#include "public/platform/WebScrollIntoViewParams.h"
namespace blink {
// Used by flexible boxes when flexing this element and by table cells.
typedef WTF::HashMap<const LayoutBox*, LayoutUnit> OverrideSizeMap;
// Size of border belt for autoscroll. When mouse pointer in border belt,
// autoscroll is started.
static const int kAutoscrollBeltSize = 20;
static const unsigned kBackgroundObscurationTestMaxDepth = 4;
struct SameSizeAsLayoutBox : public LayoutBoxModelObject {
LayoutRect frame_rect;
LayoutSize previous_size;
LayoutUnit intrinsic_content_logical_height;
LayoutRectOutsets margin_box_outsets;
LayoutUnit preferred_logical_width[2];
void* pointers[3];
};
static_assert(sizeof(LayoutBox) == sizeof(SameSizeAsLayoutBox),
"LayoutBox should stay small");
LayoutBox::LayoutBox(ContainerNode* node)
: LayoutBoxModelObject(node),
intrinsic_content_logical_height_(-1),
min_preferred_logical_width_(-1),
max_preferred_logical_width_(-1),
inline_box_wrapper_(nullptr) {
SetIsBox();
}
PaintLayerType LayoutBox::LayerTypeRequired() const {
// hasAutoZIndex only returns true if the element is positioned or a flex-item
// since position:static elements that are not flex-items get their z-index
// coerced to auto.
if (IsPositioned() || CreatesGroup() || HasClipPath() ||
HasTransformRelatedProperty() || HasHiddenBackface() || HasReflection() ||
Style()->SpecifiesColumns() || Style()->IsStackingContext() ||
Style()->ShouldCompositeForCurrentAnimations() ||
RootScrollerUtil::IsEffective(*this))
return kNormalPaintLayer;
if (HasOverflowClip())
return kOverflowClipPaintLayer;
return kNoPaintLayer;
}
void LayoutBox::WillBeDestroyed() {
ClearOverrideSize();
ClearContainingBlockOverrideSize();
if (IsOutOfFlowPositioned())
LayoutBlock::RemovePositionedObject(this);
RemoveFromPercentHeightContainer();
if (IsOrthogonalWritingModeRoot() && !DocumentBeingDestroyed())
UnmarkOrthogonalWritingModeRoot();
ShapeOutsideInfo::RemoveInfo(*this);
LayoutBoxModelObject::WillBeDestroyed();
}
void LayoutBox::InsertedIntoTree() {
LayoutBoxModelObject::InsertedIntoTree();
AddScrollSnapMapping();
if (IsOrthogonalWritingModeRoot())
MarkOrthogonalWritingModeRoot();
}
void LayoutBox::WillBeRemovedFromTree() {
if (!DocumentBeingDestroyed() && IsOrthogonalWritingModeRoot())
UnmarkOrthogonalWritingModeRoot();
ClearScrollSnapMapping();
LayoutBoxModelObject::WillBeRemovedFromTree();
}
void LayoutBox::RemoveFloatingOrPositionedChildFromBlockLists() {
DCHECK(IsFloatingOrOutOfFlowPositioned());
if (DocumentBeingDestroyed())
return;
if (IsFloating()) {
LayoutBlockFlow* parent_block_flow = nullptr;
for (LayoutObject* curr = Parent(); curr; curr = curr->Parent()) {
if (curr->IsLayoutBlockFlow()) {
LayoutBlockFlow* curr_block_flow = ToLayoutBlockFlow(curr);
if (!parent_block_flow || curr_block_flow->ContainsFloat(this))
parent_block_flow = curr_block_flow;
}
}
if (parent_block_flow) {
parent_block_flow->MarkSiblingsWithFloatsForLayout(this);
parent_block_flow->MarkAllDescendantsWithFloatsForLayout(this, false);
}
}
if (IsOutOfFlowPositioned())
LayoutBlock::RemovePositionedObject(this);
}
void LayoutBox::StyleWillChange(StyleDifference diff,
const ComputedStyle& new_style) {
const ComputedStyle* old_style = Style();
if (old_style) {
LayoutFlowThread* flow_thread = FlowThreadContainingBlock();
if (flow_thread && flow_thread != this)
flow_thread->FlowThreadDescendantStyleWillChange(this, diff, new_style);
// The background of the root element or the body element could propagate up
// to the canvas. Just dirty the entire canvas when our style changes
// substantially.
if ((diff.NeedsFullPaintInvalidation() || diff.NeedsLayout()) &&
GetNode() && (IsDocumentElement() || IsHTMLBodyElement(*GetNode()))) {
View()->SetShouldDoFullPaintInvalidation();
if (old_style->HasEntirelyFixedBackground() !=
new_style.HasEntirelyFixedBackground())
View()->Compositor()->SetNeedsUpdateFixedBackground();
}
// When a layout hint happens and an object's position style changes, we
// have to do a layout to dirty the layout tree using the old position
// value now.
if (diff.NeedsFullLayout() && Parent() &&
old_style->GetPosition() != new_style.GetPosition()) {
if (!old_style->HasOutOfFlowPosition() &&
new_style.HasOutOfFlowPosition()) {
// We're about to go out of flow. Before that takes place, we need to
// mark the current containing block chain for preferred widths
// recalculation.
SetNeedsLayoutAndPrefWidthsRecalc(
LayoutInvalidationReason::kStyleChange);
} else {
MarkContainerChainForLayout();
}
if (old_style->GetPosition() == EPosition::kStatic)
SetShouldDoFullPaintInvalidation();
else if (new_style.HasOutOfFlowPosition())
Parent()->SetChildNeedsLayout();
if (IsFloating() && !IsOutOfFlowPositioned() &&
new_style.HasOutOfFlowPosition())
RemoveFloatingOrPositionedChildFromBlockLists();
}
// FIXME: This branch runs when !oldStyle, which means that layout was never
// called so what's the point in invalidating the whole view that we never
// painted?
} else if (IsBody()) {
View()->SetShouldDoFullPaintInvalidation();
}
LayoutBoxModelObject::StyleWillChange(diff, new_style);
}
void LayoutBox::StyleDidChange(StyleDifference diff,
const ComputedStyle* old_style) {
// Horizontal writing mode definition is updated in LayoutBoxModelObject::
// updateFromStyle, (as part of the LayoutBoxModelObject::styleDidChange call
// below). So, we can safely cache the horizontal writing mode value before
// style change here.
bool old_horizontal_writing_mode = IsHorizontalWritingMode();
LayoutBoxModelObject::StyleDidChange(diff, old_style);
// Reflection works through PaintLayer. Some child classes e.g. LayoutSVGBlock
// don't create layers and ignore reflections.
if (HasReflection() && !HasLayer())
SetHasReflection(false);
if (IsFloatingOrOutOfFlowPositioned() && old_style &&
!old_style->IsFloating() && !old_style->HasOutOfFlowPosition() &&
Parent() && Parent()->IsLayoutBlockFlow())
ToLayoutBlockFlow(Parent())->ChildBecameFloatingOrOutOfFlow(this);
const ComputedStyle& new_style = StyleRef();
if (NeedsLayout() && old_style)
RemoveFromPercentHeightContainer();
if (old_horizontal_writing_mode != IsHorizontalWritingMode()) {
if (old_style) {
if (IsOrthogonalWritingModeRoot())
MarkOrthogonalWritingModeRoot();
else
UnmarkOrthogonalWritingModeRoot();
}
ClearPercentHeightDescendants();
}
// If our zoom factor changes and we have a defined scrollLeft/Top, we need to
// adjust that value into the new zoomed coordinate space. Note that the new
// scroll offset may be outside the normal min/max range of the scrollable
// area, which is weird but OK, because the scrollable area will update its
// min/max in updateAfterLayout().
if (HasOverflowClip() && old_style &&
old_style->EffectiveZoom() != new_style.EffectiveZoom()) {
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
DCHECK(scrollable_area);
// We use getScrollOffset() rather than scrollPosition(), because scroll
// offset is the distance from the beginning of flow for the box, which is
// the dimension we want to preserve.
ScrollOffset old_offset = scrollable_area->GetScrollOffset();
if (old_offset.Width() || old_offset.Height()) {
ScrollOffset new_offset = old_offset.ScaledBy(new_style.EffectiveZoom() /
old_style->EffectiveZoom());
scrollable_area->SetScrollOffsetUnconditionally(new_offset);
}
}
// Our opaqueness might have changed without triggering layout.
if (diff.NeedsFullPaintInvalidation()) {
// Invalidate self.
InvalidateBackgroundObscurationStatus();
LayoutObject* parent_to_invalidate = Parent();
// Also invalidate up to kBackgroundObscurationTestMaxDepth parents.
// This constant corresponds to a descendant walk of the same depth;
// see ComputeBackgroundIsKnownToBeObscured.
for (unsigned i = 0;
i < kBackgroundObscurationTestMaxDepth && parent_to_invalidate; ++i) {
parent_to_invalidate->InvalidateBackgroundObscurationStatus();
parent_to_invalidate = parent_to_invalidate->Parent();
}
}
if (IsDocumentElement() || IsBody()) {
GetDocument().View()->RecalculateScrollbarOverlayColorTheme(
GetDocument().View()->DocumentBackgroundColor());
GetDocument().View()->RecalculateCustomScrollbarStyle();
if (LayoutView* layout_view = View()) {
if (PaintLayerScrollableArea* scrollable_area =
layout_view->GetScrollableArea()) {
if (scrollable_area->HorizontalScrollbar() &&
scrollable_area->HorizontalScrollbar()->IsCustomScrollbar())
scrollable_area->HorizontalScrollbar()->StyleChanged();
if (scrollable_area->VerticalScrollbar() &&
scrollable_area->VerticalScrollbar()->IsCustomScrollbar())
scrollable_area->VerticalScrollbar()->StyleChanged();
}
}
}
UpdateShapeOutsideInfoAfterStyleChange(*Style(), old_style);
UpdateGridPositionAfterStyleChange(old_style);
// When we're no longer a flex item because we're now absolutely positioned,
// we need to clear the override size so we're not affected by it anymore.
// This technically covers too many cases (even when out-of-flow did not
// change) but that should be harmless.
if (IsOutOfFlowPositioned() && Parent() &&
Parent()->StyleRef().IsDisplayFlexibleOrGridBox())
ClearOverrideSize();
if (LayoutMultiColumnSpannerPlaceholder* placeholder = SpannerPlaceholder())
placeholder->LayoutObjectInFlowThreadStyleDidChange(old_style);
UpdateBackgroundAttachmentFixedStatusAfterStyleChange();
if (old_style) {
LayoutFlowThread* flow_thread = FlowThreadContainingBlock();
if (flow_thread && flow_thread != this)
flow_thread->FlowThreadDescendantStyleDidChange(this, diff, *old_style);
UpdateScrollSnapMappingAfterStyleChange(&new_style, old_style);
if (ShouldClipOverflow()) {
// The overflow clip paint property depends on border sizes through
// overflowClipRect(), and border radii, so we update properties on
// border size or radii change.
if (!old_style->BorderSizeEquals(new_style) ||
!old_style->RadiiEqual(new_style))
SetNeedsPaintPropertyUpdate();
}
}
if (diff.TransformChanged()) {
if (ScrollingCoordinator* scrolling_coordinator =
GetDocument().GetFrame()->GetPage()->GetScrollingCoordinator())
scrolling_coordinator->NotifyTransformChanged(*this);
}
// Non-atomic inlines should be LayoutInline or LayoutText, not LayoutBox.
DCHECK(!IsInline() || IsAtomicInlineLevel());
}
void LayoutBox::UpdateBackgroundAttachmentFixedStatusAfterStyleChange() {
if (!GetFrameView())
return;
// On low-powered/mobile devices, preventing blitting on a scroll can cause
// noticeable delays when scrolling a page with a fixed background image. As
// an optimization, assuming there are no fixed positoned elements on the
// page, we can acclerate scrolling (via blitting) if we ignore the CSS
// property "background-attachment: fixed".
bool ignore_fixed_background_attachment =
RuntimeEnabledFeatures::FastMobileScrollingEnabled();
if (ignore_fixed_background_attachment)
return;
// An object needs to be repainted on frame scroll when it has background-
// attachment:fixed, unless the background will be separately composited.
// LayoutView is responsible for painting root background, thus the root
// element (and the body element if html element has no background) skips
// painting backgrounds.
bool is_background_attachment_fixed_object =
!IsDocumentElement() && !BackgroundStolenForBeingBody() &&
StyleRef().HasFixedBackgroundImage();
if (IsLayoutView() &&
View()->Compositor()->PreferCompositingToLCDTextEnabled() &&
StyleRef().HasEntirelyFixedBackground()) {
is_background_attachment_fixed_object = false;
}
SetIsBackgroundAttachmentFixedObject(is_background_attachment_fixed_object);
}
void LayoutBox::UpdateShapeOutsideInfoAfterStyleChange(
const ComputedStyle& style,
const ComputedStyle* old_style) {
const ShapeValue* shape_outside = style.ShapeOutside();
const ShapeValue* old_shape_outside =
old_style ? old_style->ShapeOutside()
: ComputedStyleInitialValues::InitialShapeOutside();
Length shape_margin = style.ShapeMargin();
Length old_shape_margin =
old_style ? old_style->ShapeMargin()
: ComputedStyleInitialValues::InitialShapeMargin();
float shape_image_threshold = style.ShapeImageThreshold();
float old_shape_image_threshold =
old_style ? old_style->ShapeImageThreshold()
: ComputedStyleInitialValues::InitialShapeImageThreshold();
// FIXME: A future optimization would do a deep comparison for equality. (bug
// 100811)
if (shape_outside == old_shape_outside && shape_margin == old_shape_margin &&
shape_image_threshold == old_shape_image_threshold)
return;
if (!shape_outside)
ShapeOutsideInfo::RemoveInfo(*this);
else
ShapeOutsideInfo::EnsureInfo(*this).MarkShapeAsDirty();
if (shape_outside || shape_outside != old_shape_outside)
MarkShapeOutsideDependentsForLayout();
}
void LayoutBox::UpdateGridPositionAfterStyleChange(
const ComputedStyle* old_style) {
if (!old_style || !Parent() || !Parent()->IsLayoutGrid())
return;
if (old_style->GridColumnStart() == Style()->GridColumnStart() &&
old_style->GridColumnEnd() == Style()->GridColumnEnd() &&
old_style->GridRowStart() == Style()->GridRowStart() &&
old_style->GridRowEnd() == Style()->GridRowEnd() &&
old_style->Order() == Style()->Order() &&
old_style->HasOutOfFlowPosition() == Style()->HasOutOfFlowPosition())
return;
// Positioned items don't participate on the layout of the grid,
// so we don't need to mark the grid as dirty if they change positions.
if (old_style->HasOutOfFlowPosition() && Style()->HasOutOfFlowPosition())
return;
// It should be possible to not dirty the grid in some cases (like moving an
// explicitly placed grid item).
// For now, it's more simple to just always recompute the grid.
ToLayoutGrid(Parent())->DirtyGrid();
}
void LayoutBox::UpdateScrollSnapMappingAfterStyleChange(
const ComputedStyle* new_style,
const ComputedStyle* old_style) {
SnapCoordinator* snap_coordinator = GetDocument().GetSnapCoordinator();
if (!snap_coordinator)
return;
// Scroll snap type has no effect on the viewport defining element instead
// they are handled by the LayoutView.
bool allows_snap_container =
GetNode() != GetDocument().ViewportDefiningElement();
ScrollSnapType old_snap_type =
old_style ? old_style->GetScrollSnapType() : ScrollSnapType();
ScrollSnapType new_snap_type = new_style && allows_snap_container
? new_style->GetScrollSnapType()
: ScrollSnapType();
if (old_snap_type != new_snap_type)
snap_coordinator->SnapContainerDidChange(*this, new_snap_type);
ScrollSnapAlign old_snap_align =
old_style ? old_style->GetScrollSnapAlign() : ScrollSnapAlign();
ScrollSnapAlign new_snap_align = new_style && allows_snap_container
? new_style->GetScrollSnapAlign()
: ScrollSnapAlign();
if (old_snap_align != new_snap_align)
snap_coordinator->SnapAreaDidChange(*this, new_snap_align);
}
void LayoutBox::AddScrollSnapMapping() {
UpdateScrollSnapMappingAfterStyleChange(Style(), nullptr);
}
void LayoutBox::ClearScrollSnapMapping() {
UpdateScrollSnapMappingAfterStyleChange(nullptr, Style());
}
void LayoutBox::UpdateFromStyle() {
LayoutBoxModelObject::UpdateFromStyle();
const ComputedStyle& style_to_use = StyleRef();
SetFloating(!IsOutOfFlowPositioned() && style_to_use.IsFloating());
SetHasTransformRelatedProperty(style_to_use.HasTransformRelatedProperty());
SetHasReflection(style_to_use.BoxReflect());
}
void LayoutBox::UpdateLayout() {
DCHECK(NeedsLayout());
LayoutAnalyzer::Scope analyzer(*this);
LayoutObject* child = SlowFirstChild();
if (!child) {
ClearNeedsLayout();
return;
}
LayoutState state(*this);
while (child) {
child->LayoutIfNeeded();
DCHECK(!child->NeedsLayout());
child = child->NextSibling();
}
InvalidateBackgroundObscurationStatus();
ClearNeedsLayout();
}
// More IE extensions. clientWidth and clientHeight represent the interior of
// an object excluding border and scrollbar.
DISABLE_CFI_PERF
LayoutUnit LayoutBox::ClientWidth() const {
// We need to clamp negative values. The scrollbar may be wider than the
// padding box. Another reason: While border side values are currently limited
// to 2^20px (a recent change in the code), if this limit is raised again in
// the future, we'd have ill effects of saturated arithmetic otherwise.
return (frame_rect_.Width() - BorderLeft() - BorderRight() -
VerticalScrollbarWidth())
.ClampNegativeToZero();
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::ClientHeight() const {
// We need to clamp negative values. The scrollbar may be wider than the
// padding box. Another reason: While border side values are currently limited
// to 2^20px (a recent change in the code), if this limit is raised again in
// the future, we'd have ill effects of saturated arithmetic otherwise.
return (frame_rect_.Height() - BorderTop() - BorderBottom() -
HorizontalScrollbarHeight())
.ClampNegativeToZero();
}
int LayoutBox::PixelSnappedClientWidth() const {
return SnapSizeToPixel(ClientWidth(), Location().X() + ClientLeft());
}
DISABLE_CFI_PERF
int LayoutBox::PixelSnappedClientHeight() const {
return SnapSizeToPixel(ClientHeight(), Location().Y() + ClientTop());
}
int LayoutBox::PixelSnappedOffsetWidth(const Element*) const {
return SnapSizeToPixel(OffsetWidth(), Location().X() + ClientLeft());
}
int LayoutBox::PixelSnappedOffsetHeight(const Element*) const {
return SnapSizeToPixel(OffsetHeight(), Location().Y() + ClientTop());
}
LayoutUnit LayoutBox::ScrollWidth() const {
if (HasOverflowClip())
return GetScrollableArea()->ScrollWidth();
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
if (Style()->IsLeftToRightDirection())
return std::max(ClientWidth(), LayoutOverflowRect().MaxX() - BorderLeft());
return ClientWidth() -
std::min(LayoutUnit(), LayoutOverflowRect().X() - BorderLeft());
}
LayoutUnit LayoutBox::ScrollHeight() const {
if (HasOverflowClip())
return GetScrollableArea()->ScrollHeight();
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return std::max(ClientHeight(), LayoutOverflowRect().MaxY() - BorderTop());
}
LayoutUnit LayoutBox::ScrollLeft() const {
return HasOverflowClip()
? LayoutUnit(GetScrollableArea()->ScrollPosition().X())
: LayoutUnit();
}
LayoutUnit LayoutBox::ScrollTop() const {
return HasOverflowClip()
? LayoutUnit(GetScrollableArea()->ScrollPosition().Y())
: LayoutUnit();
}
int LayoutBox::PixelSnappedScrollWidth() const {
return SnapSizeToPixel(ScrollWidth(), Location().X() + ClientLeft());
}
int LayoutBox::PixelSnappedScrollHeight() const {
if (HasOverflowClip())
return SnapSizeToPixel(GetScrollableArea()->ScrollHeight(),
Location().Y() + ClientTop());
// For objects with visible overflow, this matches IE.
// FIXME: Need to work right with writing modes.
return SnapSizeToPixel(ScrollHeight(), Location().Y() + ClientTop());
}
void LayoutBox::SetScrollLeft(LayoutUnit new_left) {
// This doesn't hit in any tests, but since the equivalent code in
// setScrollTop does, presumably this code does as well.
DisableCompositingQueryAsserts disabler;
if (!HasOverflowClip())
return;
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
FloatPoint new_position(new_left.ToFloat(),
scrollable_area->ScrollPosition().Y());
scrollable_area->ScrollToAbsolutePosition(new_position, kScrollBehaviorAuto);
}
void LayoutBox::SetScrollTop(LayoutUnit new_top) {
// Hits in
// compositing/overflow/do-not-assert-on-invisible-composited-layers.html
DisableCompositingQueryAsserts disabler;
if (!HasOverflowClip())
return;
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
FloatPoint new_position(scrollable_area->ScrollPosition().X(),
new_top.ToFloat());
scrollable_area->ScrollToAbsolutePosition(new_position, kScrollBehaviorAuto);
}
void LayoutBox::ScrollToPosition(const FloatPoint& position,
ScrollBehavior scroll_behavior) {
// This doesn't hit in any tests, but since the equivalent code in
// setScrollTop does, presumably this code does as well.
DisableCompositingQueryAsserts disabler;
if (!HasOverflowClip())
return;
GetScrollableArea()->ScrollToAbsolutePosition(position, scroll_behavior);
}
// Returns true iff we are attempting an autoscroll inside an iframe with
// scrolling="no".
static bool IsDisallowedAutoscroll(HTMLFrameOwnerElement* owner_element,
LocalFrameView* frame_view) {
if (owner_element && IsHTMLFrameElementBase(*owner_element)) {
HTMLFrameElementBase* frame_element_base =
ToHTMLFrameElementBase(owner_element);
if (Page* page = frame_view->GetFrame().GetPage()) {
return page->GetAutoscrollController().SelectionAutoscrollInProgress() &&
frame_element_base->ScrollingMode() == kScrollbarAlwaysOff;
}
}
return false;
}
void LayoutBox::ScrollRectToVisibleRecursive(
const LayoutRect& rect,
const WebScrollIntoViewParams& params) {
DCHECK(params.GetScrollType() == kProgrammaticScroll ||
params.GetScrollType() == kUserScroll);
// Presumably the same issue as in setScrollTop. See crbug.com/343132.
DisableCompositingQueryAsserts disabler;
LayoutRect rect_to_scroll = rect;
if (rect_to_scroll.Width() <= 0)
rect_to_scroll.SetWidth(LayoutUnit(1));
if (rect_to_scroll.Height() <= 0)
rect_to_scroll.SetHeight(LayoutUnit(1));
LayoutBox* parent_box = nullptr;
LayoutRect new_rect = rect_to_scroll;
bool restricted_by_line_clamp = false;
if (ContainingBlock()) {
parent_box = ContainingBlock();
restricted_by_line_clamp =
!ContainingBlock()->Style()->LineClamp().IsNone();
}
if (!IsLayoutView() && HasOverflowClip() && !restricted_by_line_clamp) {
// Don't scroll to reveal an overflow layer that is restricted by the
// -webkit-line-clamp property. This will prevent us from revealing text
// hidden by the slider in Safari RSS.
// TODO(eae): We probably don't need this any more as we don't share any
// code with the Safari RSS reeder.
new_rect = GetScrollableArea()->ScrollIntoView(rect_to_scroll, params);
} else if (!parent_box && CanBeProgramaticallyScrolled()) {
if (LocalFrameView* frame_view = GetFrameView()) {
HTMLFrameOwnerElement* owner_element = GetDocument().LocalOwner();
if (!IsDisallowedAutoscroll(owner_element, frame_view)) {
if (params.make_visible_in_visual_viewport) {
// RootFrameViewport::ScrollIntoView expects a rect in layout
// viewport content coordinates.
if (IsLayoutView() && GetFrame()->IsMainFrame() &&
RuntimeEnabledFeatures::RootLayerScrollingEnabled()) {
rect_to_scroll.Move(
LayoutSize(GetScrollableArea()->GetScrollOffset()));
}
rect_to_scroll = frame_view->GetScrollableArea()->ScrollIntoView(
rect_to_scroll, params);
} else {
rect_to_scroll =
frame_view->LayoutViewportScrollableArea()->ScrollIntoView(
rect_to_scroll, params);
}
if (params.is_for_scroll_sequence)
rect_to_scroll.Move(PendingOffsetToScroll());
if (owner_element && owner_element->GetLayoutObject()) {
if (frame_view->SafeToPropagateScrollToParent()) {
parent_box = owner_element->GetLayoutObject()->EnclosingBox();
LayoutView* parent_view = owner_element->GetLayoutObject()->View();
new_rect = EnclosingLayoutRect(
View()
->LocalToAncestorQuad(
FloatRect(rect_to_scroll), parent_view,
kUseTransforms | kTraverseDocumentBoundaries)
.BoundingBox());
} else {
parent_box = nullptr;
}
}
}
}
}
// If we are fixed-position and stick to the viewport, it is useless to
// scroll the parent.
if (Style()->GetPosition() == EPosition::kFixed &&
ContainerForFixedPosition() == View()) {
return;
}
if (GetFrame()
->GetPage()
->GetAutoscrollController()
.SelectionAutoscrollInProgress())
parent_box = EnclosingScrollableBox();
if (parent_box) {
parent_box->ScrollRectToVisibleRecursive(new_rect, params);
} else if (GetFrame()->IsLocalRoot() && !GetFrame()->IsMainFrame()) {
LocalFrameView* frame_view = GetFrameView();
if (frame_view && frame_view->SafeToPropagateScrollToParent()) {
frame_view->ScrollRectToVisibleInRemoteParent(new_rect, params);
}
}
}
void LayoutBox::SetMargin(const NGPhysicalBoxStrut& box) {
margin_box_outsets_.SetTop(box.top);
margin_box_outsets_.SetRight(box.right);
margin_box_outsets_.SetBottom(box.bottom);
margin_box_outsets_.SetLeft(box.left);
}
void LayoutBox::AbsoluteRects(Vector<IntRect>& rects,
const LayoutPoint& accumulated_offset) const {
rects.push_back(PixelSnappedIntRect(accumulated_offset, Size()));
}
void LayoutBox::AbsoluteQuads(Vector<FloatQuad>& quads,
MapCoordinatesFlags mode) const {
if (LayoutFlowThread* flow_thread = FlowThreadContainingBlock()) {
flow_thread->AbsoluteQuadsForDescendant(*this, quads, mode);
return;
}
quads.push_back(
LocalToAbsoluteQuad(FloatRect(0, 0, frame_rect_.Width().ToFloat(),
frame_rect_.Height().ToFloat()),
mode));
}
FloatRect LayoutBox::LocalBoundingBoxRectForAccessibility() const {
return FloatRect(0, 0, frame_rect_.Width().ToFloat(),
frame_rect_.Height().ToFloat());
}
void LayoutBox::UpdateAfterLayout() {
// Transform-origin depends on box size, so we need to update the layer
// transform after layout.
if (HasLayer()) {
Layer()->UpdateTransformationMatrix();
Layer()->UpdateSizeAndScrollingAfterLayout();
}
}
LayoutUnit LayoutBox::LogicalHeightWithVisibleOverflow() const {
if (!overflow_ || HasOverflowClip())
return LogicalHeight();
LayoutRect overflow = LayoutOverflowRect();
if (Style()->IsHorizontalWritingMode())
return overflow.MaxY();
return overflow.MaxX();
}
LayoutUnit LayoutBox::ConstrainLogicalWidthByMinMax(LayoutUnit logical_width,
LayoutUnit available_width,
LayoutBlock* cb) const {
const ComputedStyle& style_to_use = StyleRef();
if (!style_to_use.LogicalMaxWidth().IsMaxSizeNone())
logical_width = std::min(
logical_width,
ComputeLogicalWidthUsing(kMaxSize, style_to_use.LogicalMaxWidth(),
available_width, cb));
return std::max(logical_width, ComputeLogicalWidthUsing(
kMinSize, style_to_use.LogicalMinWidth(),
available_width, cb));
}
LayoutUnit LayoutBox::ConstrainLogicalHeightByMinMax(
LayoutUnit logical_height,
LayoutUnit intrinsic_content_height) const {
const ComputedStyle& style_to_use = StyleRef();
if (!style_to_use.LogicalMaxHeight().IsMaxSizeNone()) {
LayoutUnit max_h = ComputeLogicalHeightUsing(
kMaxSize, style_to_use.LogicalMaxHeight(), intrinsic_content_height);
if (max_h != -1)
logical_height = std::min(logical_height, max_h);
}
return std::max(logical_height, ComputeLogicalHeightUsing(
kMinSize, style_to_use.LogicalMinHeight(),
intrinsic_content_height));
}
LayoutUnit LayoutBox::ConstrainContentBoxLogicalHeightByMinMax(
LayoutUnit logical_height,
LayoutUnit intrinsic_content_height) const {
// If the min/max height and logical height are both percentages we take
// advantage of already knowing the current resolved percentage height
// to avoid recursing up through our containing blocks again to determine it.
const ComputedStyle& style_to_use = StyleRef();
if (!style_to_use.LogicalMaxHeight().IsMaxSizeNone()) {
if (style_to_use.LogicalMaxHeight().GetType() == kPercent &&
style_to_use.LogicalHeight().GetType() == kPercent) {
LayoutUnit available_logical_height(
logical_height / style_to_use.LogicalHeight().Value() * 100);
logical_height = std::min(logical_height,
ValueForLength(style_to_use.LogicalMaxHeight(),
available_logical_height));
} else {
LayoutUnit max_height(ComputeContentLogicalHeight(
kMaxSize, style_to_use.LogicalMaxHeight(), intrinsic_content_height));
if (max_height != -1)
logical_height = std::min(logical_height, max_height);
}
}
if (style_to_use.LogicalMinHeight().GetType() == kPercent &&
style_to_use.LogicalHeight().GetType() == kPercent) {
LayoutUnit available_logical_height(
logical_height / style_to_use.LogicalHeight().Value() * 100);
logical_height =
std::max(logical_height, ValueForLength(style_to_use.LogicalMinHeight(),
available_logical_height));
} else {
logical_height = std::max(
logical_height,
ComputeContentLogicalHeight(kMinSize, style_to_use.LogicalMinHeight(),
intrinsic_content_height));
}
return logical_height;
}
void LayoutBox::SetLocationAndUpdateOverflowControlsIfNeeded(
const LayoutPoint& location) {
if (!HasLayer()) {
SetLocation(location);
return;
}
// The Layer does not yet have the up to date subpixel accumulation
// so we base the size strictly on the frame rect's location.
IntSize old_pixel_snapped_border_rect_size =
PixelSnappedBorderBoxRect().Size();
SetLocation(location);
if (PixelSnappedBorderBoxRect().Size() !=
old_pixel_snapped_border_rect_size) {
Layer()->UpdateSizeAndScrollingAfterLayout();
}
}
IntRect LayoutBox::AbsoluteContentBox() const {
// This is wrong with transforms and flipped writing modes.
IntRect rect = PixelSnappedIntRect(ContentBoxRect());
FloatPoint abs_pos = LocalToAbsolute();
rect.Move(abs_pos.X(), abs_pos.Y());
return rect;
}
IntSize LayoutBox::AbsoluteContentBoxOffset() const {
IntPoint offset = RoundedIntPoint(ContentBoxOffset());
FloatPoint abs_pos = LocalToAbsolute();
offset.Move(abs_pos.X(), abs_pos.Y());
return ToIntSize(offset);
}
FloatQuad LayoutBox::AbsoluteContentQuad(MapCoordinatesFlags flags) const {
LayoutRect rect = ContentBoxRect();
return LocalToAbsoluteQuad(FloatRect(rect), flags);
}
LayoutRect LayoutBox::BackgroundRect(BackgroundRectType rect_type) const {
EFillBox background_box = EFillBox::kText;
// Find the largest background rect of the given opaqueness.
if (const FillLayer* current = &(Style()->BackgroundLayers())) {
do {
const FillLayer* cur = current;
current = current->Next();
if (rect_type == kBackgroundKnownOpaqueRect) {
if (cur->BlendMode() != WebBlendMode::kNormal ||
cur->Composite() != kCompositeSourceOver)
continue;
bool layer_known_opaque = false;
// Check if the image is opaque and fills the clip.
if (const StyleImage* image = cur->GetImage()) {
if ((cur->RepeatX() == EFillRepeat::kRepeatFill ||
cur->RepeatX() == EFillRepeat::kRoundFill) &&
(cur->RepeatY() == EFillRepeat::kRepeatFill ||
cur->RepeatY() == EFillRepeat::kRoundFill) &&
image->KnownToBeOpaque(GetDocument(), StyleRef())) {
layer_known_opaque = true;
}
}
// The background color is painted into the last layer.
if (!cur->Next()) {
Color background_color =
ResolveColor(GetCSSPropertyBackgroundColor());
if (!background_color.HasAlpha())
layer_known_opaque = true;
}
// If neither the image nor the color are opaque then skip this layer.
if (!layer_known_opaque)
continue;
}
EFillBox current_clip = cur->Clip();
// Restrict clip if attachment is local.
if (current_clip == EFillBox::kBorder &&
cur->Attachment() == EFillAttachment::kLocal)
current_clip = EFillBox::kPadding;
// If we're asking for the clip rect, a content-box clipped fill layer can
// be scrolled into the padding box of the overflow container.
if (rect_type == kBackgroundClipRect &&
current_clip == EFillBox::kContent &&
cur->Attachment() == EFillAttachment::kLocal) {
current_clip = EFillBox::kPadding;
}
background_box = EnclosingFillBox(background_box, current_clip);
} while (current);
}
switch (background_box) {
case EFillBox::kBorder:
return BorderBoxRect();
break;
case EFillBox::kPadding:
return PaddingBoxRect();
break;
case EFillBox::kContent:
return ContentBoxRect();
break;
default:
break;
}
return LayoutRect();
}
void LayoutBox::AddOutlineRects(Vector<LayoutRect>& rects,
const LayoutPoint& additional_offset,
IncludeBlockVisualOverflowOrNot) const {
rects.push_back(LayoutRect(additional_offset, Size()));
}
bool LayoutBox::CanResize() const {
// We need a special case for <iframe> because they never have
// hasOverflowClip(). However, they do "implicitly" clip their contents, so
// we want to allow resizing them also.
return (HasOverflowClip() || IsLayoutIFrame()) &&
Style()->Resize() != EResize::kNone;
}
void LayoutBox::AddLayerHitTestRects(
LayerHitTestRects& layer_rects,
const PaintLayer* current_layer,
const LayoutPoint& layer_offset,
TouchAction supported_fast_actions,
const LayoutRect& container_rect,
TouchAction container_whitelisted_touch_action) const {
LayoutPoint adjusted_layer_offset = layer_offset + LocationOffset();
LayoutBoxModelObject::AddLayerHitTestRects(
layer_rects, current_layer, adjusted_layer_offset, supported_fast_actions,
container_rect, container_whitelisted_touch_action);
}
void LayoutBox::ComputeSelfHitTestRects(Vector<LayoutRect>& rects,
const LayoutPoint& layer_offset) const {
if (!Size().IsEmpty())
rects.push_back(LayoutRect(layer_offset, Size()));
}
int LayoutBox::VerticalScrollbarWidth() const {
if (!HasOverflowClip() || Style()->OverflowY() == EOverflow::kOverlay)
return 0;
return GetScrollableArea()->VerticalScrollbarWidth();
}
int LayoutBox::HorizontalScrollbarHeight() const {
if (!HasOverflowClip() || Style()->OverflowX() == EOverflow::kOverlay)
return 0;
return GetScrollableArea()->HorizontalScrollbarHeight();
}
LayoutUnit LayoutBox::VerticalScrollbarWidthClampedToContentBox() const {
LayoutUnit width(VerticalScrollbarWidth());
DCHECK_GE(width, LayoutUnit());
if (width) {
LayoutUnit minimum_width = LogicalWidth() - BorderAndPaddingLogicalWidth();
DCHECK_GE(minimum_width, LayoutUnit());
width = std::min(width, minimum_width);
}
return width;
}
ScrollResult LayoutBox::Scroll(ScrollGranularity granularity,
const FloatSize& delta) {
// Presumably the same issue as in setScrollTop. See crbug.com/343132.
DisableCompositingQueryAsserts disabler;
if (!GetScrollableArea())
return ScrollResult();
return GetScrollableArea()->UserScroll(granularity, delta);
}
bool LayoutBox::CanBeScrolledAndHasScrollableArea() const {
return CanBeProgramaticallyScrolled() &&
(PixelSnappedScrollHeight() != PixelSnappedClientHeight() ||
PixelSnappedScrollWidth() != PixelSnappedClientWidth());
}
bool LayoutBox::CanBeProgramaticallyScrolled() const {
Node* node = GetNode();
if (node && node->IsDocumentNode())
return true;
if (!HasOverflowClip())
return false;
bool has_scrollable_overflow =
HasScrollableOverflowX() || HasScrollableOverflowY();
if (ScrollsOverflow() && has_scrollable_overflow)
return true;
return node && HasEditableStyle(*node);
}
void LayoutBox::Autoscroll(const IntPoint& position_in_root_frame) {
LocalFrame* frame = GetFrame();
if (!frame)
return;
LocalFrameView* frame_view = frame->View();
if (!frame_view)
return;
IntPoint position_in_content =
frame_view->RootFrameToContents(position_in_root_frame);
ScrollRectToVisibleRecursive(
LayoutRect(position_in_content, LayoutSize(1, 1)),
WebScrollIntoViewParams(ScrollAlignment::kAlignToEdgeIfNeeded,
ScrollAlignment::kAlignToEdgeIfNeeded,
kUserScroll));
}
// There are two kinds of layoutObject that can autoscroll.
bool LayoutBox::CanAutoscroll() const {
if (GetNode() && GetNode()->IsDocumentNode())
return View()->GetFrameView()->IsScrollable();
// Check for a box that can be scrolled in its own right.
return CanBeScrolledAndHasScrollableArea();
}
// If specified point is in border belt, returned offset denotes direction of
// scrolling.
IntSize LayoutBox::CalculateAutoscrollDirection(
const IntPoint& point_in_root_frame) const {
if (!GetFrame())
return IntSize();
LocalFrameView* frame_view = GetFrame()->View();
if (!frame_view)
return IntSize();
LayoutRect box(AbsoluteBoundingBoxRect());
// TODO(bokan): This is wrong. Subtracting the scroll offset would get you to
// frame coordinates (pre-RLS) but *adding* the scroll offset to an absolute
// location never makes sense (and we assume below it's in content
// coordinates).
box.Move(View()->GetFrameView()->ScrollOffsetInt());
// Exclude scrollbars so the border belt (activation area) starts from the
// scrollbar-content edge rather than the window edge.
ExcludeScrollbars(box, kExcludeOverlayScrollbarSizeForHitTesting);
IntRect window_box =
View()->GetFrameView()->ContentsToRootFrame(PixelSnappedIntRect(box));
IntPoint window_autoscroll_point = point_in_root_frame;
if (window_autoscroll_point.X() < window_box.X() + kAutoscrollBeltSize)
window_autoscroll_point.Move(-kAutoscrollBeltSize, 0);
else if (window_autoscroll_point.X() >
window_box.MaxX() - kAutoscrollBeltSize)
window_autoscroll_point.Move(kAutoscrollBeltSize, 0);
if (window_autoscroll_point.Y() < window_box.Y() + kAutoscrollBeltSize)
window_autoscroll_point.Move(0, -kAutoscrollBeltSize);
else if (window_autoscroll_point.Y() >
window_box.MaxY() - kAutoscrollBeltSize)
window_autoscroll_point.Move(0, kAutoscrollBeltSize);
return window_autoscroll_point - point_in_root_frame;
}
LayoutBox* LayoutBox::FindAutoscrollable(LayoutObject* layout_object) {
while (layout_object && !(layout_object->IsBox() &&
ToLayoutBox(layout_object)->CanAutoscroll())) {
// Do not start autoscroll when the node is inside a fixed-position element.
if (layout_object->IsBox() && ToLayoutBox(layout_object)->HasLayer() &&
ToLayoutBox(layout_object)->Layer()->FixedToViewport()) {
return nullptr;
}
if (!layout_object->Parent() &&
layout_object->GetNode() == layout_object->GetDocument() &&
layout_object->GetDocument().LocalOwner())
layout_object =
layout_object->GetDocument().LocalOwner()->GetLayoutObject();
else
layout_object = layout_object->Parent();
}
return layout_object && layout_object->IsBox() ? ToLayoutBox(layout_object)
: nullptr;
}
void LayoutBox::DispatchFakeMouseMoveEventSoon(EventHandler& event_handler) {
const LayoutBoxModelObject& container = ContainerForPaintInvalidation();
FloatQuad quad =
FloatQuad(FloatRect(VisualRectIncludingCompositedScrolling(container)));
quad = container.LocalToAbsoluteQuad(quad);
event_handler.DispatchFakeMouseMoveEventSoonInQuad(quad);
}
void LayoutBox::ScrollByRecursively(const ScrollOffset& delta) {
if (delta.IsZero())
return;
bool restricted_by_line_clamp = false;
if (Parent())
restricted_by_line_clamp = !Parent()->Style()->LineClamp().IsNone();
if (HasOverflowClip() && !restricted_by_line_clamp) {
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
DCHECK(scrollable_area);
ScrollOffset new_scroll_offset = scrollable_area->GetScrollOffset() + delta;
scrollable_area->SetScrollOffset(new_scroll_offset, kProgrammaticScroll);
// If this layer can't do the scroll we ask the next layer up that can
// scroll to try.
ScrollOffset remaining_scroll_offset =
new_scroll_offset - scrollable_area->GetScrollOffset();
if (!remaining_scroll_offset.IsZero() && Parent()) {
if (LayoutBox* scrollable_box = EnclosingScrollableBox())
scrollable_box->ScrollByRecursively(remaining_scroll_offset);
LocalFrame* frame = GetFrame();
if (frame && frame->GetPage())
frame->GetPage()
->GetAutoscrollController()
.UpdateAutoscrollLayoutObject();
}
} else if (View()->GetFrameView()) {
// If we are here, we were called on a layoutObject that can be
// programmatically scrolled, but doesn't have an overflow clip. Which means
// that it is a document node that can be scrolled.
// FIXME: Pass in DoubleSize. crbug.com/414283.
View()->GetFrameView()->ScrollBy(delta, kUserScroll);
// FIXME: If we didn't scroll the whole way, do we want to try looking at
// the frames ownerElement?
// https://bugs.webkit.org/show_bug.cgi?id=28237
}
}
bool LayoutBox::NeedsPreferredWidthsRecalculation() const {
return Style()->PaddingStart().IsPercentOrCalc() ||
Style()->PaddingEnd().IsPercentOrCalc();
}
IntSize LayoutBox::OriginAdjustmentForScrollbars() const {
IntSize size;
int adjustment_width = VerticalScrollbarWidth();
if (HasFlippedBlocksWritingMode() ||
(IsHorizontalWritingMode() &&
ShouldPlaceBlockDirectionScrollbarOnLogicalLeft())) {
size.Expand(adjustment_width, 0);
}
return size;
}
IntSize LayoutBox::ScrolledContentOffset() const {
DCHECK(HasOverflowClip());
DCHECK(HasLayer());
// FIXME: Return DoubleSize here. crbug.com/414283.
PaintLayerScrollableArea* scrollable_area = GetScrollableArea();
IntSize result =
scrollable_area->ScrollOffsetInt() + OriginAdjustmentForScrollbars();
if (IsHorizontalWritingMode() &&
ShouldPlaceBlockDirectionScrollbarOnLogicalLeft())
result.Expand(-VerticalScrollbarWidth(), 0);
return result;
}
LayoutRect LayoutBox::ClippingRect(const LayoutPoint& location) const {
LayoutRect result = LayoutRect(LayoutRect::InfiniteIntRect());
if (ShouldClipOverflow())
result = OverflowClipRect(location);
if (HasClip())
result.Intersect(ClipRect(location));
return result;
}
bool LayoutBox::MapVisualRectToContainer(
const LayoutObject* container_object,
const LayoutPoint& container_offset,
const LayoutObject* ancestor,
VisualRectFlags visual_rect_flags,
TransformState& transform_state) const {
bool container_preserve_3d = container_object->Style()->Preserves3D();
TransformState::TransformAccumulation accumulation =
container_preserve_3d ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform;
// If there is no transform on this box, adjust for container offset and
// container scrolling, then apply container clip.
if (!ShouldUseTransformFromContainer(container_object)) {
transform_state.MoveBy(container_offset, accumulation);
if (container_object->IsBox() && container_object != ancestor &&
!ToLayoutBox(container_object)
->MapScrollingContentsRectToBoxSpace(transform_state, accumulation,
visual_rect_flags))
return false;
return true;
}
// Otherwise, do the following:
// 1. Expand for pixel snapping.
// 2. Generate transformation matrix combining, in this order
// a) transform,
// b) container offset,
// c) container scroll offset,
// d) perspective applied by container.
// 3. Apply transform Transform+flattening.
// 4. Apply container clip.
// 1. Expand for pixel snapping.
// Use EnclosingBoundingBox because we cannot properly compute pixel
// snapping for painted elements within the transform since we don't know
// the desired subpixel accumulation at this point, and the transform may
// include a scale. This only makes sense for non-preserve3D.
if (!StyleRef().Preserves3D()) {
transform_state.Flatten();
transform_state.SetQuad(
FloatQuad(transform_state.LastPlanarQuad().EnclosingBoundingBox()));
}
// 2. Generate transformation matrix.
// a) Transform.
TransformationMatrix transform;
if (Layer() && Layer()->Transform())
transform.Multiply(Layer()->CurrentTransform());
// b) Container offset.
transform.PostTranslate(container_offset.X().ToFloat(),
container_offset.Y().ToFloat());
// c) Container scroll offset.
if (container_object->IsBox() && container_object != ancestor &&
container_object->HasOverflowClip()) {
IntSize offset = -ToLayoutBox(container_object)->ScrolledContentOffset();
transform.PostTranslate(offset.Width(), offset.Height());
}
// d) Perspective applied by container.
if (container_object && container_object->HasLayer() &&
container_object->Style()->HasPerspective()) {
// Perspective on the container affects us, so we have to factor it in here.
DCHECK(container_object->HasLayer());
FloatPoint perspective_origin =
ToLayoutBoxModelObject(container_object)->Layer()->PerspectiveOrigin();
TransformationMatrix perspective_matrix;
perspective_matrix.ApplyPerspective(
container_object->Style()->Perspective());
perspective_matrix.ApplyTransformOrigin(perspective_origin.X(),
perspective_origin.Y(), 0);
transform = perspective_matrix * transform;
}
// 3. Apply transform and flatten.
transform_state.ApplyTransform(transform, accumulation);
if (!container_preserve_3d)
transform_state.Flatten();
// 4. Apply container clip.
if (container_object->IsBox() && container_object != ancestor &&
container_object->HasClipRelatedProperty()) {
return ToLayoutBox(container_object)
->ApplyBoxClips(transform_state, accumulation, visual_rect_flags);
}
return true;
}
bool LayoutBox::MapScrollingContentsRectToBoxSpace(
TransformState& transform_state,
TransformState::TransformAccumulation accumulation,
VisualRectFlags visual_rect_flags) const {
if (!HasClipRelatedProperty())
return true;
if (HasOverflowClip()) {
LayoutSize offset = LayoutSize(-ScrolledContentOffset());
transform_state.Move(offset, accumulation);
}
return ApplyBoxClips(transform_state, accumulation, visual_rect_flags);
}
bool LayoutBox::ApplyBoxClips(
TransformState& transform_state,
TransformState::TransformAccumulation accumulation,
VisualRectFlags visual_rect_flags) const {
// This won't work fully correctly for fixed-position elements, who should
// receive CSS clip but for whom the current object is not in the containing
// block chain.
LayoutRect clip_rect = ClippingRect(LayoutPoint());
transform_state.Flatten();
LayoutRect rect(transform_state.LastPlanarQuad().EnclosingBoundingBox());
bool does_intersect;
if (visual_rect_flags & kEdgeInclusive) {
does_intersect = rect.InclusiveIntersect(clip_rect);
} else {
rect.Intersect(clip_rect);
does_intersect = !rect.IsEmpty();
}
transform_state.SetQuad(FloatQuad(FloatRect(rect)));
return does_intersect;
}
void LayoutBox::ComputeIntrinsicLogicalWidths(
LayoutUnit& min_logical_width,
LayoutUnit& max_logical_width) const {
min_logical_width =
MinPreferredLogicalWidth() - BorderAndPaddingLogicalWidth();
max_logical_width =
MaxPreferredLogicalWidth() - BorderAndPaddingLogicalWidth();
}
LayoutUnit LayoutBox::MinPreferredLogicalWidth() const {
if (PreferredLogicalWidthsDirty()) {
#if DCHECK_IS_ON()
SetLayoutNeededForbiddenScope layout_forbidden_scope(
const_cast<LayoutBox&>(*this));
#endif
const_cast<LayoutBox*>(this)->ComputePreferredLogicalWidths();
DCHECK(!PreferredLogicalWidthsDirty());
}
return min_preferred_logical_width_;
}
DISABLE_CFI_PERF
LayoutUnit LayoutBox::MaxPreferredLogicalWidth() const {
if (PreferredLogicalWidthsDirty()) {
#if DCHECK_IS_ON()
SetLayoutNeededForbiddenScope layout_forbidden_scope(
const_cast<LayoutBox&>(*this));
#endif
const_cast<LayoutBox*>(this)->ComputePreferredLogicalWidths();
DCHECK(!PreferredLogicalWidthsDirty());
}
return max_preferred_logical_width_;
}
bool LayoutBox::HasOverrideLogicalContentHeight() const {
return rare_data_ && rare_data_->override_logical_content_height_ != -1;
}
bool LayoutBox::HasOverrideLogicalContentWidth() const {
return rare_data_ && rare_data_->override_logical_content_width_ != -1;
}
void LayoutBox::SetOverrideLogicalContentHeight(LayoutUnit height) {
DCHECK_GE(height, 0);
EnsureRareData().override_logical_content_height_ = height;
}
void LayoutBox::SetOverrideLogicalContentWidth(LayoutUnit width) {
DCHECK_GE(width, 0);
EnsureRareData().override_logical_content_width_ = width;
}
void LayoutBox::ClearOverrideLogicalContentHeight() {
if (rare_data_)
rare_data_->override_logical_content_height_ = LayoutUnit(-1);
}
void LayoutBox::ClearOverrideLogicalContentWidth() {
if (rare_data_)
rare_data_->override_logical_content_width_ = LayoutUnit(-1);
}
void LayoutBox::ClearOverrideSize() {
ClearOverrideLogicalContentHeight();
ClearOverrideLogicalContentWidth();
}
LayoutUnit LayoutBox::OverrideLogicalContentWidth() const {
DCHECK(HasOverrideLogicalContentWidth());
return rare_data_->override_logical_content_width_;
}
LayoutUnit LayoutBox::OverrideLogicalContentHeight() const {
DCHECK(HasOverrideLogicalContentHeight());
return rare_data_->override_logical_content_height_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
LayoutUnit LayoutBox::OverrideContainingBlockContentLogicalWidth() const {
DCHECK(HasOverrideContainingBlockLogicalWidth());
return rare_data_->override_containing_block_content_logical_width_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
LayoutUnit LayoutBox::OverrideContainingBlockContentLogicalHeight() const {
DCHECK(HasOverrideContainingBlockLogicalHeight());
return rare_data_->override_containing_block_content_logical_height_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
bool LayoutBox::HasOverrideContainingBlockLogicalWidth() const {
return rare_data_ &&
rare_data_->has_override_containing_block_content_logical_width_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
bool LayoutBox::HasOverrideContainingBlockLogicalHeight() const {
return rare_data_ &&
rare_data_->has_override_containing_block_content_logical_height_;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::SetOverrideContainingBlockContentLogicalWidth(
LayoutUnit logical_width) {
DCHECK_GE(logical_width, LayoutUnit(-1));
EnsureRareData().override_containing_block_content_logical_width_ =
logical_width;
EnsureRareData().has_override_containing_block_content_logical_width_ = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::SetOverrideContainingBlockContentLogicalHeight(
LayoutUnit logical_height) {
DCHECK_GE(logical_height, LayoutUnit(-1));
EnsureRareData().override_containing_block_content_logical_height_ =
logical_height;
EnsureRareData().has_override_containing_block_content_logical_height_ = true;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::ClearContainingBlockOverrideSize() {
if (!rare_data_)
return;
EnsureRareData().has_override_containing_block_content_logical_width_ = false;
EnsureRareData().has_override_containing_block_content_logical_height_ =
false;
}
// TODO (lajava) Shouldn't we implement these functions based on physical
// direction ?.
void LayoutBox::ClearOverrideContainingBlockContentLogicalHeight() {
if (!rare_data_)
return;
EnsureRareData().has_override_containing_block_content_logical_height_ =
false;
}
LayoutUnit LayoutBox::AdjustBorderBoxLogicalWidthForBoxSizing(
float width) const {
LayoutUnit borders_plus_padding = CollapsedBorderAndCSSPaddingLogicalWidth();
LayoutUnit result(width);
if (Style()->BoxSizing() == EBoxSizing::kContentBox)
return result + borders_plus_padding;
return std::max(result, borders_plus_padding);
}
LayoutUnit LayoutBox::AdjustBorderBoxLogicalHeightForBoxSizing(
float height) const {
LayoutUnit borders_plus_padding = CollapsedBorderAndCSSPaddingLogicalHeight();
LayoutUnit result(height);
if (Style()->BoxSizing() == EBoxSizing::kContentBox)
return result + borders_plus_padding;
return std::max(result, borders_plus_padding);
}
LayoutUnit LayoutBox::AdjustContentBoxLogicalWidthForBoxSizing(
float width) const {
LayoutUnit result(width);
if (Style()->BoxSizing() == EBoxSizing::kBorderBox)
result -= CollapsedBorderAndCSSPaddingLogicalWidth();
return std::max(LayoutUnit(), result);
}
LayoutUnit LayoutBox::AdjustContentBoxLogicalHeightForBoxSizing(
float height) const {
LayoutUnit result(height);
if (Style()->BoxSizing() == EBoxSizing::kBorderBox)
result -= CollapsedBorderAndCSSPaddingLogicalHeight();
return std::max(LayoutUnit(), result);
}
// Hit Testing
bool LayoutBox::NodeAtPoint(HitTestResult& result,
const HitTestLocation& location_in_container,
const LayoutPoint& accumulated_offset,
HitTestAction action) {
LayoutPoint adjusted_location = accumulated_offset + Location();
if (!RootScrollerUtil::IsEffective(*this)) {
// Check if we need to do anything at all.
// If we have clipping, then we can't have any spillout.
LayoutRect overflow_box =
HasOverflowClip() ? BorderBoxRect() : VisualOverflowRect();
FlipForWritingMode(overflow_box);
overflow_box.MoveBy(adjusted_location);
if (!location_in_container.Intersects(overflow_box))
return false;
}
bool should_hit_test_self = IsInSelfHitTestingPhase(action);
if (should_hit_test_self && HasOverflowClip() &&
HitTestOverflowControl(result, location_in_container, adjusted_location))
return true;
bool skip_children = false;
if (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 (!HasSelfPaintingLayer() &&
!location_in_container.Intersects(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, location_in_container,
adjusted_location, action)) {
return true;
}
if (Style()->HasBorderRadius() &&
HitTestClippedOutByBorder(location_in_container, adjusted_location))
return false;
// Now hit test ourselves.
if (should_hit_test_self &&
VisibleToHitTestRequest(result.GetHitTestRequest())) {
LayoutRect bounds_rect(adjusted_location, Size());
if (location_in_container.Intersects(bounds_rect)) {
UpdateHitTestResult(result,
FlipForWritingMode(location_in_container.Point() -
ToLayoutSize(adjusted_location)));
if (result.AddNodeToListBasedTestResult(NodeForHitTest(),
location_in_container,
bounds_rect) == kStopHitTesting)
return true;
}
}
return false;
}
bool LayoutBox::HitTestChildren(HitTestResult& result,
const HitTestLocation& location_in_container,
const LayoutPoint& accumulated_offset,
HitTestAction action) {
for (LayoutObject* child = SlowLastChild(); child;
child = child->PreviousSibling()) {
if ((!child->HasLayer() ||
!ToLayoutBoxModelObject(child)->Layer()->IsSelfPaintingLayer()) &&
child->NodeAtPoint(result, location_in_container, accumulated_offset,
action))
return true;
}
return false;
}
bool LayoutBox::HitTestClippedOutByBorder(
const HitTestLocation& location_in_container,
const LayoutPoint& border_box_location) const {
LayoutRect border_rect = BorderBoxRect();
border_rect.MoveBy(border_box_location);
return !location_in_container.Intersects(
Style()->GetRoundedBorderFor(border_rect));
}
void LayoutBox::Paint(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
BoxPainter(*this).Paint(paint_info, paint_offset);
}
void LayoutBox::PaintBoxDecorationBackground(
const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
BoxPainter(*this).PaintBoxDecorationBackground(paint_info, paint_offset);
}
bool LayoutBox::GetBackgroundPaintedExtent(LayoutRect& painted_extent) const {
DCHECK(StyleRef().HasBackground());
// LayoutView is special in the sense that it expands to the whole canvas,
// thus can't be handled by this function.
DCHECK(!IsLayoutView());
LayoutRect background_rect(BorderBoxRect());
Color background_color = ResolveColor(GetCSSPropertyBackgroundColor());
if (background_color.Alpha()) {
painted_extent = background_rect;
return true;
}
if (!Style()->BackgroundLayers().GetImage() ||
Style()->BackgroundLayers().Next()) {
painted_extent = background_rect;
return true;
}
BackgroundImageGeometry geometry(*this);
// TODO(jchaffraix): This function should be rethought as it's called during
// and outside of the paint phase. Potentially returning different results at
// different phases.
geometry.Calculate(nullptr, PaintPhase::kBlockBackground,
kGlobalPaintNormalPhase, Style()->BackgroundLayers(),
background_rect);
if (geometry.HasNonLocalGeometry())
return false;
painted_extent = LayoutRect(geometry.DestRect());
return true;
}
bool LayoutBox::BackgroundIsKnownToBeOpaqueInRect(
const LayoutRect& local_rect) const {
if (IsDocumentElement() || BackgroundStolenForBeingBody())
return false;
// If the element has appearance, it might be painted by theme.
// We cannot be sure if theme paints the background opaque.
// In this case it is safe to not assume opaqueness.
// FIXME: May be ask theme if it paints opaque.
if (Style()->HasAppearance())
return false;
// FIXME: Check the opaqueness of background images.
// FIXME: Use rounded rect if border radius is present.
if (Style()->HasBorderRadius())
return false;
if (HasClipPath())
return false;
if (Style()->HasBlendMode())
return false;
return BackgroundRect(kBackgroundKnownOpaqueRect).Contains(local_rect);
}
static bool IsCandidateForOpaquenessTest(const LayoutBox& child_box) {
const ComputedStyle& child_style = child_box.StyleRef();
if (child_style.GetPosition() != EPosition::kStatic &&
child_box.ContainingBlock() != child_box.Parent())
return false;
if (child_style.Visibility() != EVisibility::kVisible ||
child_style.ShapeOutside())
return false;
if (child_box.Size().IsZero())
return false;
if (PaintLayer* child_layer = child_box.Layer()) {
// FIXME: perhaps this could be less conservative?
if (child_layer->GetCompositingState() != kNotComposited)
return false;
// FIXME: Deal with z-index.
if (child_style.IsStackingContext())
return false;
if (child_layer->HasTransformRelatedProperty() ||
child_layer->IsTransparent() ||
child_layer->HasFilterInducingProperty())
return false;
if (child_box.HasOverflowClip() && child_style.HasBorderRadius())
return false;
}
return true;
}
bool LayoutBox::ForegroundIsKnownToBeOpaqueInRect(
const LayoutRect& local_rect,
unsigned max_depth_to_test) const {
if (!max_depth_to_test)
return false;
for (LayoutObject* child = SlowFirstChild(); child;
child = child->NextSibling()) {
if (!child->IsBox())
continue;
LayoutBox* child_box = ToLayoutBox(child);
if (!IsCandidateForOpaquenessTest(*child_box))
continue;
LayoutPoint child_location = child_box->Location();
if (child_box->IsInFlowPositioned())
child_location.Move(child_box->OffsetForInFlowPosition());
LayoutRect child_local_rect = local_rect;
child_local_rect.MoveBy(-child_location);
if (child_local_rect.Y() < 0 || child_local_rect.X() < 0) {
// If there is unobscured area above/left of a static positioned box then
// the rect is probably not covered.
if (!child_box->IsPositioned())
return false;
continue;
}
if (child_local_rect.MaxY() > child_box->Size().Height() ||
child_local_rect.MaxX() > child_box->Size().Width())
continue;
if (child_box->BackgroundIsKnownToBeOpaqueInRect(child_local_rect))
return true;
if (child_box->ForegroundIsKnownToBeOpaqueInRect(child_local_rect,
max_depth_to_test - 1))
return true;
}
return false;
}
DISABLE_CFI_PERF
bool LayoutBox::ComputeBackgroundIsKnownToBeObscured() const {
if (ScrollsOverflow())
return false;
// Test to see if the children trivially obscure the background.
if (!StyleRef().HasBackground())
return false;
// Root background painting is special.
if (IsLayoutView())
return false;
// FIXME: box-shadow is painted while background painting.
if (Style()->BoxShadow())
return false;
LayoutRect background_rect;
if (!GetBackgroundPaintedExtent(background_rect))
return false;
return ForegroundIsKnownToBeOpaqueInRect(background_rect,
kBackgroundObscurationTestMaxDepth);
}
void LayoutBox::PaintMask(const PaintInfo& paint_info,
const LayoutPoint& paint_offset) const {
BoxPainter(*this).PaintMask(paint_info, paint_offset);
}
void LayoutBox::ImageChanged(WrappedImagePtr image,
CanDeferInvalidation defer,
const IntRect*) {
// TODO(chrishtr): support PaintInvalidationReason::kDelayedFull for animated
// border images.
if ((StyleRef().BorderImage().GetImage() &&
StyleRef().BorderImage().GetImage()->Data() == image) ||
(StyleRef().MaskBoxImage().GetImage() &&
StyleRef().MaskBoxImage().GetImage()->Data() == image) ||
(StyleRef().BoxReflect() && StyleRef().BoxReflect()->Mask().GetImage() &&
StyleRef().BoxReflect()->Mask().GetImage()->Data() == image)) {
SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kImage);
} else {
for (const FillLayer* layer = &StyleRef().MaskLayers(); layer;
layer = layer->Next()) {
if (layer->GetImage() && image == layer->GetImage()->Data()) {
SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kImage);
break;
}
}
}
if (!IsDocumentElement() && !BackgroundStolenForBeingBody()) {
for (const FillLayer* layer = &StyleRef().BackgroundLayers(); layer;
layer = layer->Next()) {
if (layer->GetImage() && image == layer->GetImage()->Data()) {
InvalidateBackgroundObscurationStatus();
bool maybe_animated =
layer->GetImage()->CachedImage() &&
layer->GetImage()->CachedImage()->GetImage() &&
layer->GetImage()->CachedImage()->GetImage()->MaybeAnimated();
if (defer == CanDeferInvalidation::kYes && maybe_animated) {
SetMayNeedPaintInvalidationAnimatedBackgroundImage();
} else {
SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kImage);
SetBackgroundChangedSinceLastPaintInvalidation();
}
break;
}
}
}
ShapeValue* shape_outside_value = Style()->ShapeOutside();
if (!GetFrameView()->IsInPerformLayout() && IsFloating() &&
shape_outside_value && shape_outside_value->GetImage() &&
shape_outside_value->GetImage()->Data() == image) {
ShapeOutsideInfo& info = ShapeOutsideInfo::EnsureInfo(*this);
if (!info.IsComputingShape()) {
info.MarkShapeAsDirty();
MarkShapeOutsideDependentsForLayout();
}
}
}
ResourcePriority LayoutBox::ComputeResourcePriority() const {
LayoutRect view_bounds = ViewRect();
LayoutRect object_bounds = LayoutRect(AbsoluteContentBox());
// The object bounds might be empty right now, so intersects will fail since
// it doesn't deal with empty rects. Use LayoutRect::contains in that case.
bool is_visible;
if (!object_bounds.IsEmpty())
is_visible = view_bounds.Intersects(object_bounds);
else
is_visible = view_bounds.Contains(object_bounds);
LayoutRect screen_rect;
if (!object_bounds.IsEmpty()) {
screen_rect = view_bounds;
screen_rect.Intersect(object_bounds);
}
int screen_area = 0;
if (!screen_rect.IsEmpty() && is_visible)
screen_area = (screen_rect.Width() * screen_rect.Height()).ToInt();
return ResourcePriority(
is_visible ? ResourcePriority::kVisible : ResourcePriority::kNotVisible,
screen_area);
}
void LayoutBox::LocationChanged() {
// The location may change because of layout of other objects. Should check
// this object for paint invalidation.
if (!NeedsLayout())
SetMayNeedPaintInvalidation();
}
void LayoutBox::SizeChanged() {
// The size may change because of layout of other objects. Should check this
// object for paint invalidation.
if (!NeedsLayout())
SetMayNeedPaintInvalidation();
if (GetNode() && GetNode()->IsElementNode()) {
Element& element = ToElement(*GetNode());
element.SetNeedsResizeObserverUpdate();
}
}
bool LayoutBox::IntersectsVisibleViewport() const {
LayoutRect rect = VisualOverflowRect();
LayoutView* layout_view = View();
while (layout_view->GetFrame()->OwnerLayoutObject())
layout_view = layout_view->GetFrame()->OwnerLayoutObject()->View();
MapToVisualRectInAncestorSpace(layout_view, rect);
return rect.Intersects(LayoutRect(
layout_view->GetFrameView()->GetScrollableArea()->VisibleContentRect()));
}
void LayoutBox::EnsureIsReadyForPaintInvalidation() {
LayoutBoxModelObject::EnsureIsReadyForPaintInvalidation();
if (MayNeedPaintInvalidationAnimatedBackgroundImage() &&
!BackgroundIsKnownToBeObscured()) {
SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kDelayedFull);
}
if (FullPaintInvalidationReason() != PaintInvalidationReason::kDelayedFull ||
!IntersectsVisibleViewport())
return;
// Do regular full paint invalidation if the object with
// PaintInvalidationReason::kDelayedFull is onscreen.
// Conservatively assume the delayed paint invalidation was caused by
// background image change.
SetBackgroundChangedSinceLastPaintInvalidation();
SetShouldDoFullPaintInvalidationWithoutGeometryChange(
PaintInvalidationReason::kFull);
}
PaintInvalidationReason LayoutBox::InvalidatePaint(
const PaintInvalidatorContext& context) const {
return BoxPaintInvalidator(*this, context).InvalidatePaint();
}
LayoutRect LayoutBox::OverflowClipRect(
const LayoutPoint& location,
OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
if (RootScrollerUtil::IsEffective(*this))
return View()->ViewRect();
// FIXME: When overflow-clip (CSS3) is implemented, we'll obtain the property
// here.
LayoutRect clip_rect = BorderBoxRect();
clip_rect.SetLocation(location + clip_rect.Location() +
LayoutSize(BorderLeft(), BorderTop()));
clip_rect.SetSize(clip_rect.Size() -
LayoutSize(BorderWidth(), BorderHeight()));
if (HasOverflowClip())
ExcludeScrollbars(clip_rect, overlay_scrollbar_clip_behavior);
if (HasControlClip())
clip_rect.Intersect(ControlClipRect(location));
return clip_rect;
}
void LayoutBox::ExcludeScrollbars(
LayoutRect& rect,
OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) {
if (ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) {
rect.Move(scrollable_area->VerticalScrollbarWidth(
overlay_scrollbar_clip_behavior),
0);
}
rect.Contract(scrollable_area->VerticalScrollbarWidth(
overlay_scrollbar_clip_behavior),
scrollable_area->HorizontalScrollbarHeight(
overlay_scrollbar_clip_behavior));
}
}
LayoutRect LayoutBox::ClipRect(const LayoutPoint& location) const {
LayoutRect border_box_rect = BorderBoxRect();
LayoutRect clip_rect =
LayoutRect(border_box_rect.Location() + location, border_box_rect.Size());
if (!Style()->ClipLeft().IsAuto()) {
LayoutUnit c = ValueForLength(Style()->ClipLeft(), border_box_rect.Width());
clip_rect.Move(c, LayoutUnit());
clip_rect.Contract(c, LayoutUnit());
}
if (!Style()->ClipRight().IsAuto())
clip_rect.Contract(
Size().Width() - ValueForLength(Style()->ClipRight(), Size().Width()),
LayoutUnit());
if (!Style()->ClipTop().IsAuto()) {
LayoutUnit c = ValueForLength(Style()->ClipTop(), border_box_rect.Height());
clip_rect.Move(LayoutUnit(), c);
clip_rect.Contract(LayoutUnit(), c);
}
if (!Style()->ClipBottom().IsAuto()) {
clip_rect.Contract(LayoutUnit(),
Size().Height() - ValueForLength(Style()->ClipBottom(),
Size().Height()));
}
return clip_rect;
}
static LayoutUnit PortionOfMarginNotConsumedByFloat(LayoutUnit child_margin,
LayoutUnit content_side,
LayoutUnit offset) {
if (child_margin <= 0)
return LayoutUnit();
LayoutUnit content_side_with_margin = content_side + child_margin;
if (offset > content_side_with_margin)
return child_margin;
return offset - content_side;
}
LayoutUnit LayoutBox::ShrinkLogicalWidthToAvoidFloats(
LayoutUnit child_margin_start,
LayoutUnit child_margin_end,
const LayoutBlockFlow* cb) const {
LayoutUnit logical_top_position = LogicalTop();
LayoutUnit start_offset_for_content = cb->StartOffsetForContent();
LayoutUnit end_offset_for_content = cb->EndOffsetForContent();
LayoutUnit logical_height = cb->LogicalHeightForChild(*this);
LayoutUnit start_offset_for_line = cb->StartOffsetForLine(
logical_top_position, kDoNotIndentText, logical_height);
LayoutUnit end_offset_for_line = cb->EndOffsetForLine(
logical_top_position, kDoNotIndentText, logical_height);
// If there aren't any floats constraining us then allow the margins to
// shrink/expand the width as much as they want.
if (start_offset_for_content == start_offset_for_line &&
end_offset_for_content == end_offset_for_line)
return cb->AvailableLogicalWidthForLine(logical_top_position,
kDoNotIndentText, logical_height) -
child_margin_start - child_margin_end;
LayoutUnit width = cb->AvailableLogicalWidthForLine(
logical_top_position, kDoNotIndentText, logical_height);
// This section of code is just for a use counter. It counts if something
// that avoids floats may have been affected by a float with shape-outside.
// NOTE(ikilpatrick): This isn't the only code-path which uses
// AvailableLogicalWidthForLine, if we switch this over we need to inspect
// other usages.
if (!ShapeOutsideInfo::IsEmpty()) {
LayoutUnit alternate_width = cb->AvailableLogicalWidthForAvoidingFloats(
logical_top_position, logical_height);
if (alternate_width != width) {
UseCounter::Count(GetDocument(),
WebFeature::kShapeOutsideMaybeAffectedInlineSize);
}
}
width -= std::max(LayoutUnit(), child_margin_start);
width -= std::max(LayoutUnit(), child_margin_end);
// We need to see if margins on either the start side or the end side can
// contain the floats in question. If they can, then just using the line width
// is inaccurate. In the case where a float completely fits, we don't need to
// use the line offset at all, but can instead push all the way to the content
// edge of the containing block. In the case where the float doesn't fit, we
// can use the line offset, but we need to grow it by the margin to reflect
// the fact that the margin was "consumed" by the float. Negative margins
// aren't consumed by the float, and so we ignore them.
width += PortionOfMarginNotConsumedByFloat(
child_margin_start, start_offset_for_content, start_offset_for_line);
width += PortionOfMarginNotConsumedByFloat(
child_margin_end, end_offset_for_content, end_offset_for_line);
return width;
}
LayoutUnit LayoutBox::ContainingBlockLogicalHeightForGetComputedStyle() const {
if (HasOverrideContainingBlockLogicalHeight())
return OverrideContainingBlockContentLogicalHeight();
if (!IsPositioned())
return ContainingBlockLogicalHeightForContent(kExcludeMarginBorderPadding);
LayoutBoxModelObject* cb = ToLayoutBoxModelObject(Container());
LayoutUnit height = ContainingBlockLogicalHeightForPositioned(cb);
if (StyleRef().GetPosition() != EPosition::kAbsolute)
height -= cb->PaddingLogicalHeight();
return height;
}
LayoutUnit LayoutBox::ContainingBlockLogicalWidthForContent() const {
if (HasOverrideContainingBlockLogicalWidth())
return OverrideContainingBlockContentLogicalWidth();
LayoutBlock* cb = ContainingBlock();
if (IsOutOfFlowPositioned())
return cb->ClientLogicalWidth();
return cb->AvailableLogicalWidth();
}
LayoutUnit LayoutBox::ContainingBlockLogicalHeightForContent(
AvailableLogicalHeightType height_type) const {
if (HasOverrideContainingBlockLogicalHeight())
return OverrideContainingBlockContentLogicalHeight();
LayoutBlock* cb = ContainingBlock();
return cb->AvailableLogicalHeight(height_type);
}
LayoutUnit LayoutBox::ContainingBlockAvailableLineWidth() const {
LayoutBlock* cb = ContainingBlock();
if (cb->IsLayoutBlockFlow())
return ToLayoutBlockFlow(cb)->AvailableLogicalWidthForLine(
LogicalTop(), kDoNotIndentText,
AvailableLogicalHeight(kIncludeMarginBorderPadding));
return LayoutUnit();
}
LayoutUnit LayoutBox::PerpendicularContainingBlockLogicalHeight() const {
if (HasOverrideContainingBlockLogicalHeight())
return OverrideContainingBlockContentLogicalHeight();
LayoutBlock* cb = ContainingBlock();
if (cb->HasOverrideLogicalContentHeight())
return cb->OverrideLogicalContentHeight();
const ComputedStyle& containing_block_style = cb->StyleRef();
Length logical_height_length = containing_block_style.LogicalHeight();
// FIXME: For now just support fixed heights. Eventually should support
// percentage heights as well.
if (!logical_height_length.IsFixed()) {
LayoutUnit fill_fallback_extent =
LayoutUnit(containing_block_style.IsHorizontalWritingMode()
? View()->GetFrameView()->VisibleContentSize().Height()
: View()->GetFrameView()->VisibleContentSize().Width());
LayoutUnit fill_available_extent =
ContainingBlock()->AvailableLogicalHeight(kExcludeMarginBorderPadding);
if (fill_available_extent == -1)
return fill_fallback_extent;
return std::min(fill_available_extent, fill_fallback_extent);
}
// Use the content box logical height as specified by the style.
return cb->AdjustContentBoxLogicalHeightForBoxSizing(
LayoutUnit(logical_height_length.Value()));
}
void LayoutBox::MapLocalToAncestor(const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
MapCoordinatesFlags mode) const {
bool is_fixed_pos = Style()->GetPosition() == EPosition::kFixed;
// If this box has a transform or contains paint, it acts as a fixed position
// container for fixed descendants, and may itself also be fixed position. So
// propagate 'fixed' up only if this box is fixed position.
if (CanContainFixedPositionObjects() && !is_fixed_pos)
mode &= ~kIsFixed;
else if (is_fixed_pos)
mode |= kIsFixed;
LayoutBoxModelObject::MapLocalToAncestor(ancestor, transform_state, mode);
}
void LayoutBox::MapAncestorToLocal(const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
MapCoordinatesFlags mode) const {
if (this == ancestor)
return;
bool is_fixed_pos = Style()->GetPosition() == EPosition::kFixed;
// If this box has a transform or contains paint, it acts as a fixed position
// container for fixed descendants, and may itself also be fixed position. So
// propagate 'fixed' up only if this box is fixed position.
if (CanContainFixedPositionObjects() && !is_fixed_pos)
mode &= ~kIsFixed;
else if (is_fixed_pos)
mode |= kIsFixed;
LayoutBoxModelObject::MapAncestorToLocal(ancestor, transform_state, mode);
}
LayoutSize LayoutBox::OffsetFromContainer(const LayoutObject* o) const {
DCHECK_EQ(o, Container());
LayoutSize offset;
if (IsInFlowPositioned())
offset += OffsetForInFlowPosition();
offset += PhysicalLocationOffset();
if (o->HasOverflowClip())
offset -= ToLayoutBox(o)->ScrolledContentOffset();
if (Style()->GetPosition() == EPosition::kAbsolute &&
o->IsInFlowPositioned() && o->IsLayoutInline())
offset += ToLayoutInline(o)->OffsetForInFlowPositionedInline(*this);
return offset;
}
InlineBox* LayoutBox::CreateInlineBox() {
return new InlineBox(LineLayoutItem(this));
}
void LayoutBox::DirtyLineBoxes(bool full_layout) {
if (inline_box_wrapper_) {
if (full_layout) {
inline_box_wrapper_->Destroy();
inline_box_wrapper_ = nullptr;
} else {
inline_box_wrapper_->DirtyLineBoxes();
}
}
}
void LayoutBox::PositionLineBox(InlineBox* box) {
if (IsOutOfFlowPositioned()) {
// Cache the x position only if we were an INLINE type originally.
bool originally_inline = Style()->IsOriginalDisplayInlineType();
if (originally_inline) {
// The value is cached in the xPos of the box. We only need this value if
// our object was inline originally, since otherwise it would have ended
// up underneath the inlines.
RootInlineBox& root = box->Root();
root.Block().SetStaticInlinePositionForChild(LineLayoutBox(this),
box->LogicalLeft());
} else {
// Our object was a block originally, so we make our normal flow position
// be just below the line box (as though all the inlines that came before
// us got wrapped in an anonymous block, which is what would have happened
// had we been in flow). This value was cached in the y() of the box.
Layer()->SetStaticBlockPosition(box->LogicalTop());
}
if (Container()->IsLayoutInline())
MoveWithEdgeOfInlineContainerIfNecessary(box->IsHorizontal());
// Nuke the box.
box->Remove(kDontMarkLineBoxes);
box->Destroy();
} else if (IsAtomicInlineLevel()) {
SetLocationAndUpdateOverflowControlsIfNeeded(box->Location());
SetInlineBoxWrapper(box);
}
}
void LayoutBox::MoveWithEdgeOfInlineContainerIfNecessary(bool is_horizontal) {
DCHECK(IsOutOfFlowPositioned());
DCHECK(Container()->IsLayoutInline());
DCHECK(Container()->IsInFlowPositioned());
// If this object is inside a relative positioned inline and its inline
// position is an explicit offset from the edge of its container then it will
// need to move if its inline container has changed width. We do not track if
// the width has changed but if we are here then we are laying out lines
// inside it, so it probably has - mark our object for layout so that it can
// move to the new offset created by the new width.
if (!NormalChildNeedsLayout() &&
!Style()->HasStaticInlinePosition(is_horizontal))
SetChildNeedsLayout(kMarkOnlyThis);
}
void LayoutBox::DeleteLineBoxWrapper() {
if (inline_box_wrapper_) {
if (!DocumentBeingDestroyed())
inline_box_wrapper_->Remove();
inline_box_wrapper_->Destroy();
inline_box_wrapper_ = nullptr;
}
}
void LayoutBox::SetSpannerPlaceholder(
LayoutMultiColumnSpannerPlaceholder& placeholder) {
// Not expected to change directly from one spanner to another.
CHECK(!rare_data_ || !rare_data_->spanner_placeholder_);
EnsureRareData().spanner_placeholder_ = &placeholder;
}
void LayoutBox::ClearSpannerPlaceholder() {
if (!rare_data_)
return;
rare_data_->spanner_placeholder_ = nullptr;
}
void LayoutBox::SetPaginationStrut(LayoutUnit strut) {
if (!strut && !rare_data_)
return;
EnsureRareData().pagination_strut_ = strut;
}
bool LayoutBox::IsBreakBetweenControllable(EBreakBetween break_value) const {
if (break_value == EBreakBetween::kAuto)
return true;
// We currently only support non-auto break-before and break-after values on
// in-flow block level elements, which is the minimum requirement according to
// the spec.
if (IsInline() || IsFloatingOrOutOfFlowPositioned())
return false;
const LayoutBlock* curr = ContainingBlock();
if (!curr || !curr->IsLayoutBlockFlow())
return false;
const LayoutView* layout_view = View();
bool view_is_paginated = layout_view->FragmentationContext();
if (!view_is_paginated && !FlowThreadContainingBlock())
return false;
while (curr) {
if (curr == layout_view) {
return view_is_paginated && break_value != EBreakBetween::kColumn &&
break_value != EBreakBetween::kAvoidColumn;
}
if (curr->IsLayoutFlowThread()) {
if (break_value ==
EBreakBetween::kAvoid) // Valid in any kind of fragmentation context.
return true;
bool is_multicol_value = break_value == EBreakBetween::kColumn ||
break_value == EBreakBetween::kAvoidColumn;
if (ToLayoutFlowThread(curr)->IsLayoutPagedFlowThread())
return !is_multicol_value;
if (is_multicol_value)
return true;
// If this is a flow thread for a multicol container, and we have a break
// value for paged, we need to keep looking.
}
if (curr->IsOutOfFlowPositioned())
return false;
curr = curr->ContainingBlock();
}
NOTREACHED();
return false;
}
bool LayoutBox::IsBreakInsideControllable(EBreakInside break_value) const {
if (break_value == EBreakInside::kAuto)
return true;
// First check multicol.
const LayoutFlowThread* flow_thread = FlowThreadContainingBlock();
// 'avoid-column' is only valid in a multicol context.
if (break_value == EBreakInside::kAvoidColumn)
return flow_thread && !flow_thread->IsLayoutPagedFlowThread();
// 'avoid' is valid in any kind of fragmentation context.
if (break_value == EBreakInside::kAvoid && flow_thread)
return true;
DCHECK(break_value == EBreakInside::kAvoidPage ||
break_value == EBreakInside::kAvoid);
if (View()->FragmentationContext())
return true; // The view is paginated, probably because we're printing.
if (!flow_thread)
return false; // We're not inside any pagination context
// We're inside a flow thread. We need to be contained by a flow thread for
// paged overflow in order for pagination values to be valid, though.
for (const LayoutBlock* ancestor = flow_thread; ancestor;
ancestor = ancestor->ContainingBlock()) {
if (ancestor->IsLayoutFlowThread() &&
ToLayoutFlowThread(ancestor)->IsLayoutPagedFlowThread())
return true;
}
return false;
}
EBreakBetween LayoutBox::BreakAfter() const {
EBreakBetween break_value = Style()->BreakAfter();
if (break_value == EBreakBetween::kAuto ||
IsBreakBetweenControllable(break_value))
return break_value;
return EBreakBetween::kAuto;
}
EBreakBetween LayoutBox::BreakBefore() const {
EBreakBetween break_value = Style()->BreakBefore();
if (break_value == EBreakBetween::kAuto ||
IsBreakBetweenControllable(break_value))
return break_value;
return EBreakBetween::kAuto;
}
EBreakInside LayoutBox::BreakInside() const {
EBreakInside break_value = Style()->BreakInside();
if (break_value == EBreakInside::kAuto ||
IsBreakInsideControllable(break_value))
return break_value;
return EBreakInside::kAuto;
}
EBreakBetween LayoutBox::ClassABreakPointValue(
EBreakBetween previous_break_after_value) const {
// First assert that we're at a class A break point.
DCHECK(IsBreakBetweenControllable(previous_break_after_value));
return JoinFragmentainerBreakValues(previous_break_after_value,
BreakBefore());
}
bool LayoutBox::NeedsForcedBreakBefore(
EBreakBetween previous_break_after_value) const {
// Forced break values are only honored when specified on in-flow objects, but
// floats and out-of-flow positioned objects may be affected by a break-after
// value of the previous in-flow object, even though we're not at a class A
// break point.
EBreakBetween break_value =
IsFloatingOrOutOfFlowPositioned()
? previous_break_after_value
: ClassABreakPointValue(previous_break_after_value);
return IsForcedFragmentainerBreakValue(break_value);
}
bool LayoutBox::PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const {
if (HasNonCompositedScrollbars() ||
GetSelectionState() != SelectionState::kNone ||
HasBoxDecorationBackground() || StyleRef().HasBoxDecorations() ||
StyleRef().HasVisualOverflowingEffect())
return false;
// Both mask and clip-path generates drawing display items that depends on
// the size of the box.
if (HasMask() || HasClipPath())
return false;
// If the box has any kind of clip, we need issue paint invalidation to cover
// the changed part of children when the box got resized. In SPv175 this is
// handled by detecting paint property changes.
if (!RuntimeEnabledFeatures::SlimmingPaintV175Enabled()) {
if (HasClipRelatedProperty())
return false;
}
// If the box paints into its own backing, we can assume that it's painting
// may have some effect. For example, honoring the border-radius clip on
// a composited child paints into a mask for an otherwise non-painting
// element, because children of that element will require the mask.
if (HasLayer() && Layer()->GetCompositingState() == kPaintsIntoOwnBacking)
return false;
return true;
}
LayoutRect LayoutBox::LocalVisualRectIgnoringVisibility() const {
return SelfVisualOverflowRect();
}
void LayoutBox::InflateVisualRectForFilterUnderContainer(
TransformState& transform_state,
const LayoutObject& container,
const LayoutBoxModelObject* ancestor_to_stop_at) const {
transform_state.Flatten();
// Apply visual overflow caused by reflections and filters defined on objects
// between this object and container (not included) or ancestorToStopAt
// (included).
LayoutSize offset_from_container = OffsetFromContainer(&container);
transform_state.Move(offset_from_container);
for (LayoutObject* parent = Parent(); parent && parent != container;
parent = parent->Parent()) {
if (parent->IsBox()) {
// Convert rect into coordinate space of parent to apply parent's
// reflection and filter.
LayoutSize parent_offset =
parent->OffsetFromAncestorContainer(&container);
transform_state.Move(-parent_offset);
ToLayoutBox(parent)->InflateVisualRectForFilter(transform_state);
transform_state.Move(parent_offset);
}
if (parent == ancestor_to_stop_at)
break;
}
transform_state.Move(-offset_from_container);
}
bool LayoutBox::MapToVisualRectInAncestorSpaceInternal(
const LayoutBoxModelObject* ancestor,
TransformState& transform_state,
VisualRectFlags visual_rect_flags) const {
InflateVisualRectForFilter(transform_state);
if (ancestor == this)
return true;
AncestorSkipInfo skip_info(ancestor, true);
LayoutObject* container = Container(&skip_info);
LayoutBox* table_row_container = nullptr;
// Skip table row because cells and rows are in the same coordinate space (see
// below, however for more comments about when |ancestor| is the table row).
if (IsTableCell()) {
DCHECK(container->IsTableRow());
DCHECK_EQ(ParentBox(), container);
if (container != ancestor)
container = container->Parent();
else
table_row_container = ToLayoutBox(container);
}
if (!container)
return true;
LayoutPoint container_offset;
if (container->IsBox()) {
container_offset.MoveBy(PhysicalLocation(ToLayoutBox(container)));
// If the row is the ancestor, however, add its offset back in. In effect,
// this passes from the joint <td> / <tr> coordinate space to the parent
// space, then back to <tr> / <td>.
if (table_row_container) {
container_offset.MoveBy(
-table_row_container->PhysicalLocation(ToLayoutBox(container)));
}
} else if (container->IsRuby()) {
// TODO(wkorman): Generalize Ruby specialization and/or document more
// clearly. See the accompanying specialization in
// LayoutInline::mapToVisualRectInAncestorSpaceInternal.
container_offset.MoveBy(PhysicalLocation());
} else {
container_offset.MoveBy(Location());
}
const ComputedStyle& style_to_use = StyleRef();
EPosition position = style_to_use.GetPosition();
if (position == EPosition::kAbsolute && container->IsInFlowPositioned() &&
container->IsLayoutInline()) {
container_offset.Move(
ToLayoutInline(container)->OffsetForInFlowPositionedInline(*this));
} else if (style_to_use.HasInFlowPosition() && Layer()) {
// Apply the relative position offset when invalidating a rectangle. The
// layer is translated, but the layout box isn't, so we need to do this to
// get the right dirty rect. Since this is called from
// LayoutObject::setStyle, the relative position flag on the LayoutObject
// has been cleared, so use the one on the style().
container_offset.Move(Layer()->OffsetForInFlowPosition());
}
if (skip_info.FilterSkipped()) {
InflateVisualRectForFilterUnderContainer(transform_state, *container,
ancestor);
}
if (!MapVisualRectToContainer(container, container_offset, ancestor,
visual_rect_flags, transform_state))
return false;
if (skip_info.AncestorSkipped()) {
bool preserve3D = container->Style()->Preserves3D();
TransformState::TransformAccumulation accumulation =
preserve3D ? TransformState::kAccumulateTransform
: TransformState::kFlattenTransform;
// If the ancestor is below the container, then we need to map the rect into
// ancestor's coordinates.
LayoutSize container_offset =
ancestor->OffsetFromAncestorContainer(container);
transform_state.Move(-container_offset, accumulation);
// If the ancestor is fixed, then the rect is already in its coordinates so
// doesn't need viewport-adjusting.
if (ancestor->Style()->GetPosition() != EPosition::kFixed &&
container->IsLayoutView() && position == EPosition::kFixed) {
transform_state.Move(
ToLayoutView(container)->OffsetForFixedPosition(true), accumulation);
}
return true;
}
if (container->IsLayoutView())
return ToLayoutView(container)->MapToVisualRectInAncestorSpaceInternal(
ancestor, transform_state, position == EPosition::kFixed ? kIsFixed : 0,
visual_rect_flags);
else
return container->MapToVisualRectInAncestorSpaceInternal(
ancestor, transform_state, visual_rect_flags);
}
void LayoutBox::InflateVisualRectForFilter(
TransformState& transform_state) const {
if (!Layer() || !Layer()->PaintsWithFilters())
return;
transform_state.Flatten();
LayoutRect rect(transform_state.LastPlanarQuad().BoundingBox());
transform_state.SetQuad(
FloatQuad(FloatRect(Layer()->MapLayoutRectForFilter(rect))));
}
static bool ShouldRecalculateMinMaxWidthsAffectedByAncestor(
const LayoutBox* box) {
if (box->PreferredLogicalWidthsDirty()) {
// If the preferred widths are already dirty at this point (during layout),
// it actually means that we never need to calculate them, since that should
// have been carried out by an ancestor that's sized based on preferred
// widths (a shrink-to-fit container, for instance). In such cases the
// object will be left as dirty indefinitely, and it would just be a waste
// of time to calculate the preferred withs when nobody needs them.
return false;
}
if (const LayoutBox* containing_block = box->ContainingBlock()) {
if (containing_block->NeedsPreferredWidthsRecalculation()) {
// If our containing block also has min/max widths that are affected by
// the ancestry, we have already dealt with this object as well. Avoid
// unnecessary work and O(n^2) time complexity.
return false;
}
}
return true;
}
void LayoutBox::UpdateLogicalWidth() {
if (NeedsPreferredWidthsRecalculation()) {
if (ShouldRecalculateMinMaxWidthsAffectedByAncestor(this)) {
// Laying out this object means that its containing block is also being
// laid out. This object is special, in that its min/max widths depend on
// the ancestry (min/max width calculation should ideally be strictly
// bottom-up, but that's not always the case), so since the containing
// block size may have changed, we need to recalculate the min/max widths
// of this object, and every child that has the same issue, recursively.
SetPreferredLogicalWidthsDirty(kMarkOnlyThis);
// Since all this takes place during actual layout, instead of being part
// of min/max the width calculation machinery, we need to enter said
// machinery here, to make sure that what was dirtied is actualy
// recalculated. Leaving things dirty would mean that any subsequent
// dirtying of descendants would fail.
ComputePreferredLogicalWidths();
}
}
LogicalExtentComputedValues computed_values;
ComputeLogicalWidth(computed_values);
SetLogicalWidth(computed_values.extent_);
SetLogicalLeft(computed_values.position_);
SetMarginStart(computed_values.margins_.start_);
SetMarginEnd(computed_values.margins_.end_);
}
static float GetMaxWidthListMarker(const LayoutBox* layout_object) {
#if DCHECK_IS_ON()
DCHECK(layout_object);
Node* parent_node = layout_object->GeneratingNode();
DCHECK(parent_node);
DCHECK(IsHTMLOListElement(parent_node) || IsHTMLUListElement(parent_node));
DCHECK_NE(layout_object->Style()->TextAutosizingMultiplier(), 1);
#endif
float max_width = 0;
for (LayoutObject* child = layout_object->SlowFirstChild(); child;
child = child->NextSibling()) {
if (!child->IsListItem())
continue;
LayoutBox* list_item