| /* |
| * 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/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/LayoutAPIShim.h" |
| #include "core/layout/api/LayoutEmbeddedContentItem.h" |
| #include "core/layout/api/LineLayoutBlockFlow.h" |
| #include "core/layout/api/LineLayoutBox.h" |
| #include "core/layout/compositing/PaintLayerCompositor.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/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" |
| |
| 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() && |
| (isHTMLHtmlElement(*GetNode()) || 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); |
| |
| 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 = this->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()) { |
| LayoutObject* parent_to_invalidate = Parent(); |
| 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 = |
| this->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. 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()->SupportsFixedRootBackgroundCompositing()) { |
| if (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() |
| : ComputedStyle::InitialShapeOutside(); |
| |
| Length shape_margin = style.ShapeMargin(); |
| Length old_shape_margin = old_style ? old_style->ShapeMargin() |
| : ComputedStyle::InitialShapeMargin(); |
| |
| float shape_image_threshold = style.ShapeImageThreshold(); |
| float old_shape_image_threshold = |
| old_style ? old_style->ShapeImageThreshold() |
| : ComputedStyle::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::ScrollRectToVisible(const LayoutRect& rect, |
| const ScrollAlignment& align_x, |
| const ScrollAlignment& align_y, |
| ScrollType scroll_type, |
| bool make_visible_in_visual_viewport, |
| ScrollBehavior scroll_behavior, |
| bool is_for_scroll_sequence) { |
| DCHECK(scroll_type == kProgrammaticScroll || scroll_type == 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(); |
| } |
| |
| bool is_smooth = scroll_behavior == kScrollBehaviorSmooth || |
| (scroll_behavior == kScrollBehaviorAuto && |
| Style()->GetScrollBehavior() == kScrollBehaviorSmooth); |
| |
| 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, align_x, align_y, is_smooth, scroll_type, |
| is_for_scroll_sequence); |
| if (new_rect.IsEmpty()) |
| return; |
| } else if (!parent_box && CanBeProgramaticallyScrolled()) { |
| if (LocalFrameView* frame_view = this->GetFrameView()) { |
| HTMLFrameOwnerElement* owner_element = GetDocument().LocalOwner(); |
| if (!IsDisallowedAutoscroll(owner_element, frame_view)) { |
| if (make_visible_in_visual_viewport) { |
| if (IsLayoutView() && |
| RuntimeEnabledFeatures::RootLayerScrollingEnabled()) { |
| rect_to_scroll.Move( |
| LayoutSize(GetScrollableArea()->GetScrollOffset())); |
| } |
| rect_to_scroll = frame_view->GetScrollableArea()->ScrollIntoView( |
| rect_to_scroll, align_x, align_y, is_smooth, scroll_type, |
| is_for_scroll_sequence); |
| } else { |
| rect_to_scroll = |
| frame_view->LayoutViewportScrollableArea()->ScrollIntoView( |
| rect_to_scroll, align_x, align_y, is_smooth, scroll_type, |
| is_for_scroll_sequence); |
| } |
| if (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->ScrollRectToVisible(new_rect, align_x, align_y, scroll_type, |
| make_visible_in_visual_viewport, |
| scroll_behavior, is_for_scroll_sequence); |
| } |
| } |
| |
| 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 = kTextFillBox; |
| // 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() == kRepeatFill || cur->RepeatX() == kRoundFill) && |
| (cur->RepeatY() == kRepeatFill || cur->RepeatY() == 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(CSSPropertyBackgroundColor); |
| 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 == kBorderFillBox && |
| cur->Attachment() == kLocalBackgroundAttachment) |
| current_clip = kPaddingFillBox; |
| |
| // 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 == kContentFillBox && |
| cur->Attachment() == kLocalBackgroundAttachment) { |
| current_clip = kPaddingFillBox; |
| } |
| |
| background_box = EnclosingFillBox(background_box, current_clip); |
| } while (current); |
| } |
| switch (background_box) { |
| case kBorderFillBox: |
| return BorderBoxRect(); |
| break; |
| case kPaddingFillBox: |
| return PaddingBoxRect(); |
| break; |
| case kContentFillBox: |
| 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, |
| const LayoutRect& container_rect) const { |
| LayoutPoint adjusted_layer_offset = layer_offset + LocationOffset(); |
| LayoutBoxModelObject::AddLayerHitTestRects( |
| layer_rects, current_layer, adjusted_layer_offset, container_rect); |
| } |
| |
| 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 = this->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 = this->GetFrame(); |
| if (!frame) |
| return; |
| |
| LocalFrameView* frame_view = frame->View(); |
| if (!frame_view) |
| return; |
| |
| IntPoint position_in_content = |
| frame_view->RootFrameToContents(position_in_root_frame); |
| ScrollRectToVisible(LayoutRect(position_in_content, LayoutSize(1, 1)), |
| 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(); |
| |
| IntRect box(AbsoluteBoundingBoxRect()); |
| box.Move(View()->GetFrameView()->ScrollOffsetInt()); |
| IntRect window_box = View()->GetFrameView()->ContentsToRootFrame(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::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 = this->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 = this->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 { |
| LayoutRect result = LayoutRect(LayoutRect::InfiniteIntRect()); |
| if (ShouldClipOverflow()) |
| result = OverflowClipRect(LayoutPoint()); |
| |
| if (HasClip()) |
| result.Intersect(ClipRect(LayoutPoint())); |
| |
| 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(); |
| |
| 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; |
| |
| // TODO(pdr): We should also check for css clip in the !isSelfPaintingLayer |
| // case, similar to overflow clip below. |
| bool skip_children = false; |
| if (ShouldClipOverflow() && !HasSelfPaintingLayer()) { |
| if (!location_in_container.Intersects(OverflowClipRect( |
| adjusted_location, kExcludeOverlayScrollbarSizeForHitTesting))) { |
| skip_children = true; |
| } else if (Style()->HasBorderRadius()) { |
| LayoutRect bounds_rect(adjusted_location, Size()); |
| skip_children = !location_in_container.Intersects( |
| Style()->GetRoundedInnerBorderFor(bounds_rect)); |
| } |
| } |
| |
| // TODO(pdr): We should also include checks for hit testing border radius at |
| // the layer level (see: crbug.com/568904). |
| |
| 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(CSSPropertyBackgroundColor); |
| 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, 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, 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 (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()->OwnerLayoutItem().IsNull()) |
| layout_view = LayoutAPIShim::LayoutObjectFrom( |
| layout_view->GetFrame()->OwnerLayoutItem()) |
| ->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 = this->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 = this->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) - |
| std::max(LayoutUnit(), child_margin_start) - |
| 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; |
| } |
| |
| // At a class A break point [1], the break value with the highest precedence |
| // wins. If the two values have the same precedence (e.g. "left" and "right"), |
| // the value specified on a latter object wins. |
| // |
| // [1] https://drafts.csswg.org/css-break/#possible-breaks |
| static inline int FragmentainerBreakPrecedence(EBreakBetween break_value) { |
| // "auto" has the lowest priority. |
| // "avoid*" values win over "auto". |
| // "avoid-page" wins over "avoid-column". |
| // "avoid" wins over "avoid-page". |
| // Forced break values win over "avoid". |
| // Any forced page break value wins over "column" forced break. |
| // More specific break values (left, right, recto, verso) wins over generic |
| // "page" values. |
| |
| switch (break_value) { |
| default: |
| NOTREACHED(); |
| // fall-through |
| case EBreakBetween::kAuto: |
| return 0; |
| case EBreakBetween::kAvoidColumn: |
| return 1; |
| case EBreakBetween::kAvoidPage: |
| return 2; |
| case EBreakBetween::kAvoid: |
| return 3; |
| case EBreakBetween::kColumn: |
| return 4; |
| case EBreakBetween::kPage: |
| return 5; |
| case EBreakBetween::kLeft: |
| case EBreakBetween::kRight: |
| case EBreakBetween::kRecto: |
| case EBreakBetween::kVerso: |
| return 6; |
| } |
| } |
| |
| EBreakBetween LayoutBox::JoinFragmentainerBreakValues( |
| EBreakBetween first_value, |
| EBreakBetween second_value) { |
| if (FragmentainerBreakPrecedence(second_value) >= |
| FragmentainerBreakPrecedence(first_value)) |
| return second_value; |
| return first_value; |
| } |
| |
| 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 SPv2 this is |
| // handled by detecting paint property changes. |
| if (!RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) { |
| 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::LocalVisualRect() const { |
| if (Style()->Visibility() != EVisibility::kVisible) |
| return LayoutRect(); |
| |
| if (HasMask() && !ShouldClipOverflow() && |
| !RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) |
| return LayoutRect(Layer()->BoxForFilterOrMask()); |
| |
| 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 = this->OffsetFromContainer(&container); |
| transform_state.Move(offset_from_container); |
| for (LayoutObject* parent = this->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 = this->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 = ToLayoutBox(child); |
| for (LayoutObject* item_child = list_item->SlowFirstChild(); item_child; |
| item_child = item_child->NextSibling()) { |
| if (!item_child->IsListMarker()) |
| continue; |
| LayoutBox* item_marker = ToLayoutBox(item_child); |
| // Make sure to compute the autosized width. |
| if (item_marker->NeedsLayout()) |
| item_marker->UpdateLayout(); |
| max_width = std::max<float>( |
| max_width, ToLayoutListMarker(item_marker)->LogicalWidth().ToFloat()); |
| break; |
| } |
| } |
| return max_width; |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBox::ComputeLogicalWidth( |
| LogicalExtentComputedValues& computed_values) const { |
| computed_values.extent_ = |
| Style()->ContainsSize() ? BorderAndPaddingLogicalWidth() : LogicalWidth(); |
| computed_values.position_ = LogicalLeft(); |
| computed_values.margins_.start_ = MarginStart(); |
| computed_values.margins_.end_ = MarginEnd(); |
| |
| // The parent box is flexing us, so it has increased or decreased our |
| // width. Use the width from the style context. |
| if (HasOverrideLogicalContentWidth()) { |
| computed_values.extent_ = |
| OverrideLogicalContentWidth() + BorderAndPaddingLogicalWidth(); |
| return; |
| } |
| |
| if (IsOutOfFlowPositioned()) { |
| ComputePositionedLogicalWidth(computed_values); |
| return; |
| } |
| |
| // FIXME: Account for writing-mode in flexible boxes. |
| // https://bugs.webkit.org/show_bug.cgi?id=46418 |
| bool in_vertical_box = |
| Parent()->IsDeprecatedFlexibleBox() && |
| (Parent()->Style()->BoxOrient() == EBoxOrient::kVertical); |
| bool stretching = (Parent()->Style()->BoxAlign() == EBoxAlignment::kStretch); |
| // TODO (lajava): Stretching is the only reason why we don't want the box to |
| // be treated as a replaced element, so we could perhaps refactor all this |
| // logic, not only for flex and grid since alignment is intended to be applied |
| // to any block. |
| bool treat_as_replaced = ShouldComputeSizeAsReplaced() && |
| (!in_vertical_box || !stretching) && |
| (!IsGridItem() || !HasStretchedLogicalWidth()); |
| const ComputedStyle& style_to_use = StyleRef(); |
| Length logical_width_length = |
| treat_as_replaced ? Length(ComputeReplacedLogicalWidth(), kFixed) |
| : style_to_use.LogicalWidth(); |
| |
| LayoutBlock* cb = ContainingBlock(); |
| LayoutUnit container_logical_width = |
| std::max(LayoutUnit(), ContainingBlockLogicalWidthForContent()); |
| bool has_perpendicular_containing_block = |
| cb->IsHorizontalWritingMode() != IsHorizontalWritingMode(); |
| |
| if (IsInline() && !IsInlineBlockOrInlineTable()) { |
| // just calculate margins |
| computed_values.margins_.start_ = MinimumValueForLength( |
| style_to_use.MarginStart(), container_logical_width); |
| computed_values.margins_.end_ = MinimumValueForLength( |
| style_to_use.MarginEnd(), container_logical_width); |
| if (treat_as_replaced) |
| computed_values.extent_ = |
| std::max(LayoutUnit(FloatValueForLength(logical_width_length, 0)) + |
| BorderAndPaddingLogicalWidth(), |
| MinPreferredLogicalWidth()); |
| return; |
| } |
| |
| LayoutUnit container_width_in_inline_direction = container_logical_width; |
| if (has_perpendicular_containing_block) |
| container_width_in_inline_direction = |
| PerpendicularContainingBlockLogicalHeight(); |
| |
| // Width calculations |
| if (treat_as_replaced) { |
| computed_values.extent_ = LayoutUnit(logical_width_length.Value()) + |
| BorderAndPaddingLogicalWidth(); |
| } else { |
| LayoutUnit preferred_width = ComputeLogicalWidthUsing( |
| kMainOrPreferredSize, style_to_use.LogicalWidth(), |
| container_width_in_inline_direction, cb); |
| computed_values.extent_ = ConstrainLogicalWidthByMinMax( |
| preferred_width, container_width_in_inline_direction, cb); |
| } |
| |
| // Margin calculations. |
| ComputeMarginsForDirection( |
| kInlineDirection, cb, container_logical_width, computed_values.extent_, |
| computed_values.margins_.start_, computed_values.margins_.end_, |
| Style()->MarginStart(), Style()->MarginEnd()); |
| |
| if (!has_perpendicular_containing_block && container_logical_width && |
| container_logical_width != |
| (computed_values.extent_ + computed_values.margins_.start_ + |
| computed_values.margins_.end_) && |
| !IsFloating() && !IsInline() && !cb->IsFlexibleBoxIncludingDeprecated() && |
| !cb->IsLayoutGrid()) { |
| LayoutUnit new_margin_total = |
| container_logical_width - computed_values.extent_; |
| bool has_inverted_direction = cb->Style()->IsLeftToRightDirection() != |
| Style()->IsLeftToRightDirection(); |
| if (has_inverted_direction) { |
| computed_values.margins_.start_ = |
| new_margin_total - computed_values.margins_.end_; |
| } else { |
| computed_values.margins_.end_ = |
| new_margin_total - computed_values.margins_.start_; |
| } |
| } |
| |
| if (style_to_use.TextAutosizingMultiplier() != 1 && |
| style_to_use.MarginStart().GetType() == kFixed) { |
| Node* parent_node = GeneratingNode(); |
| if (parent_node && (isHTMLOListElement(*parent_node) || |
| isHTMLUListElement(*parent_node))) { |
| // Make sure the markers in a list are properly positioned (i.e. not |
| // chopped off) when autosized. |
| const float adjusted_margin = |
| (1 - 1.0 / style_to_use.TextAutosizingMultiplier()) * |
| GetMaxWidthListMarker(this); |
| bool has_inverted_direction = cb->Style()->IsLeftToRightDirection() != |
| Style()->IsLeftToRightDirection(); |
| if (has_inverted_direction) |
| computed_values.margins_.end_ += adjusted_margin; |
| else |
| computed_values.margins_.start_ += adjusted_margin; |
| } |
| } |
| } |
| |
| LayoutUnit LayoutBox::FillAvailableMeasure( |
| LayoutUnit available_logical_width) const { |
| LayoutUnit margin_start; |
| LayoutUnit margin_end; |
| return FillAvailableMeasure(available_logical_width, margin_start, |
| margin_end); |
| } |
| |
| LayoutUnit LayoutBox::FillAvailableMeasure(LayoutUnit available_logical_width, |
| LayoutUnit& margin_start, |
| LayoutUnit& margin_end) const { |
| DCHECK_GE(available_logical_width, 0); |
| margin_start = |
| MinimumValueForLength(Style()->MarginStart(), available_logical_width); |
| margin_end = |
| MinimumValueForLength(Style()->MarginEnd(), available_logical_width); |
| LayoutUnit available = available_logical_width - margin_start - margin_end; |
| available = std::max(available, LayoutUnit()); |
| return available; |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutUnit LayoutBox::ComputeIntrinsicLogicalWidthUsing( |
| const Length& logical_width_length, |
| LayoutUnit available_logical_width, |
| LayoutUnit border_and_padding) const { |
| if (logical_width_length.GetType() == kFillAvailable) |
| return std::max(border_and_padding, |
| FillAvailableMeasure(available_logical_width)); |
| |
| LayoutUnit min_logical_width; |
| LayoutUnit max_logical_width; |
| ComputeIntrinsicLogicalWidths(min_logical_width, max_logical_width); |
| |
| if (logical_width_length.GetType() == kMinContent) |
| return min_logical_width + border_and_padding; |
| |
| if (logical_width_length.GetType() == kMaxContent) |
| return max_logical_width + border_and_padding; |
| |
| if (logical_width_length.GetType() == kFitContent) { |
| min_logical_width += border_and_padding; |
| max_logical_width += border_and_padding; |
| return std::max(min_logical_width, |
| std::min(max_logical_width, |
| FillAvailableMeasure(available_logical_width))); |
| } |
| |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutUnit LayoutBox::ComputeLogicalWidthUsing( |
| SizeType width_type, |
| const Length& logical_width, |
| LayoutUnit available_logical_width, |
| const LayoutBlock* cb) const { |
| DCHECK(width_type == kMinSize || width_type == kMainOrPreferredSize || |
| !logical_width.IsAuto()); |
| if (width_type == kMinSize && logical_width.IsAuto()) |
| return AdjustBorderBoxLogicalWidthForBoxSizing(0); |
| |
| if (!logical_width.IsIntrinsicOrAuto()) { |
| // FIXME: If the containing block flow is perpendicular to our direction we |
| // need to use the available logical height instead. |
| return AdjustBorderBoxLogicalWidthForBoxSizing( |
| ValueForLength(logical_width, available_logical_width)); |
| } |
| |
| if (logical_width.IsIntrinsic()) |
| return ComputeIntrinsicLogicalWidthUsing( |
| logical_width, available_logical_width, BorderAndPaddingLogicalWidth()); |
| |
| LayoutUnit margin_start; |
| LayoutUnit margin_end; |
| LayoutUnit logical_width_result = |
| FillAvailableMeasure(available_logical_width, margin_start, margin_end); |
| |
| if (ShrinkToAvoidFloats() && cb->IsLayoutBlockFlow() && |
| ToLayoutBlockFlow(cb)->ContainsFloats()) |
| logical_width_result = |
| std::min(logical_width_result, |
| ShrinkLogicalWidthToAvoidFloats(margin_start, margin_end, |
| ToLayoutBlockFlow(cb))); |
| |
| if (width_type == kMainOrPreferredSize && |
| SizesLogicalWidthToFitContent(logical_width)) { |
| // Reset width so that any percent margins on inline children do not |
| // use it when calculating min/max preferred width. |
| // TODO(crbug.com/710026): Remove const_cast |
| const_cast<LayoutBox*>(this)->SetLogicalWidth(LayoutUnit()); |
| return std::max(MinPreferredLogicalWidth(), |
| std::min(MaxPreferredLogicalWidth(), logical_width_result)); |
| } |
| return logical_width_result; |
| } |
| |
| bool LayoutBox::ColumnFlexItemHasStretchAlignment() const { |
| // auto margins mean we don't stretch. Note that this function will only be |
| // used for widths, so we don't have to check marginBefore/marginAfter. |
| const auto& parent_style = Parent()->StyleRef(); |
| DCHECK(parent_style.IsColumnFlexDirection()); |
| if (StyleRef().MarginStart().IsAuto() || StyleRef().MarginEnd().IsAuto()) |
| return false; |
| return StyleRef() |
| .ResolvedAlignSelf( |
| ContainingBlock()->SelfAlignmentNormalBehavior(), |
| &parent_style) |
| .GetPosition() == kItemPositionStretch; |
| } |
| |
| bool LayoutBox::IsStretchingColumnFlexItem() const { |
| LayoutObject* parent = this->Parent(); |
| if (parent->IsDeprecatedFlexibleBox() && |
| parent->Style()->BoxOrient() == EBoxOrient::kVertical && |
| parent->Style()->BoxAlign() == EBoxAlignment::kStretch) |
| return true; |
| |
| // We don't stretch multiline flexboxes because they need to apply line |
| // spacing (align-content) first. |
| if (parent->IsFlexibleBox() && |
| parent->Style()->FlexWrap() == EFlexWrap::kNowrap && |
| parent->Style()->IsColumnFlexDirection() && |
| ColumnFlexItemHasStretchAlignment()) |
| return true; |
| return false; |
| } |
| |
| // TODO (lajava) Can/Should we move this inside specific layout classes (flex. |
| // grid)? Can we refactor columnFlexItemHasStretchAlignment logic? |
| bool LayoutBox::HasStretchedLogicalWidth() const { |
| const ComputedStyle& style = StyleRef(); |
| if (!style.LogicalWidth().IsAuto() || style.MarginStart().IsAuto() || |
| style.MarginEnd().IsAuto()) |
| return false; |
| LayoutBlock* cb = ContainingBlock(); |
| if (!cb) { |
| // We are evaluating align-self/justify-self, which default to 'normal' for |
| // the root element. The 'normal' value behaves like 'start' except for |
| // Flexbox Items, which obviously should have a container. |
| return false; |
| } |
| if (cb->IsHorizontalWritingMode() != IsHorizontalWritingMode()) |
| return style |
| .ResolvedAlignSelf(cb->SelfAlignmentNormalBehavior(this), |
| cb->Style()) |
| .GetPosition() == kItemPositionStretch; |
| return style |
| .ResolvedJustifySelf(cb->SelfAlignmentNormalBehavior(this), |
| cb->Style()) |
| .GetPosition() == kItemPositionStretch; |
| } |
| |
| bool LayoutBox::SizesLogicalWidthToFitContent( |
| const Length& logical_width) const { |
| if (IsFloating() || IsInlineBlockOrInlineTable() || |
| StyleRef().HasOutOfFlowPosition()) |
| return true; |
| |
| if (IsGridItem()) |
| return !HasStretchedLogicalWidth(); |
| |
| // Flexible box items should shrink wrap, so we lay them out at their |
| // intrinsic widths. In the case of columns that have a stretch alignment, we |
| // go ahead and layout at the stretched size to avoid an extra layout when |
| // applying alignment. |
| if (Parent()->IsFlexibleBox()) { |
| // For multiline columns, we need to apply align-content first, so we can't |
| // stretch now. |
| if (!Parent()->Style()->IsColumnFlexDirection() || |
| Parent()->Style()->FlexWrap() != EFlexWrap::kNowrap) |
| return true; |
| if (!ColumnFlexItemHasStretchAlignment()) |
| return true; |
| } |
| |
| // Flexible horizontal boxes lay out children at their intrinsic widths. Also |
| // vertical boxes that don't stretch their kids lay out their children at |
| // their intrinsic widths. |
| // FIXME: Think about writing-mode here. |
| // https://bugs.webkit.org/show_bug.cgi?id=46473 |
| if (Parent()->IsDeprecatedFlexibleBox() && |
| (Parent()->Style()->BoxOrient() == EBoxOrient::kHorizontal || |
| Parent()->Style()->BoxAlign() != EBoxAlignment::kStretch)) |
| return true; |
| |
| // Button, input, select, textarea, and legend treat width value of 'auto' as |
| // 'intrinsic' unless it's in a stretching column flexbox. |
| // FIXME: Think about writing-mode here. |
| // https://bugs.webkit.org/show_bug.cgi?id=46473 |
| if (logical_width.IsAuto() && !IsStretchingColumnFlexItem() && |
| AutoWidthShouldFitContent()) |
| return true; |
| |
| if (IsHorizontalWritingMode() != ContainingBlock()->IsHorizontalWritingMode()) |
| return true; |
| |
| return false; |
| } |
| |
| bool LayoutBox::AutoWidthShouldFitContent() const { |
| return GetNode() && |
| (isHTMLInputElement(*GetNode()) || isHTMLSelectElement(*GetNode()) || |
| isHTMLButtonElement(*GetNode()) || |
| isHTMLTextAreaElement(*GetNode()) || IsRenderedLegend()); |
| } |
| |
| void LayoutBox::ComputeMarginsForDirection(MarginDirection flow_direction, |
| const LayoutBlock* containing_block, |
| LayoutUnit container_width, |
| LayoutUnit child_width, |
| LayoutUnit& margin_start, |
| LayoutUnit& margin_end, |
| Length margin_start_length, |
| Length margin_end_length) const { |
| // First assert that we're not calling this method on box types that don't |
| // support margins. |
| DCHECK(!IsTableCell()); |
| DCHECK(!IsTableRow()); |
| DCHECK(!IsTableSection()); |
| DCHECK(!IsLayoutTableCol()); |
| if (flow_direction == kBlockDirection || IsFloating() || IsInline()) { |
| // Margins are calculated with respect to the logical width of |
| // the containing block (8.3) |
| // Inline blocks/tables and floats don't have their margins increased. |
| margin_start = MinimumValueForLength(margin_start_length, container_width); |
| margin_end = MinimumValueForLength(margin_end_length, container_width); |
| return; |
| } |
| |
| if (containing_block->IsFlexibleBox()) { |
| // We need to let flexbox handle the margin adjustment - otherwise, flexbox |
| // will think we're wider than we actually are and calculate line sizes |
| // wrong. See also http://dev.w3.org/csswg/css-flexbox/#auto-margins |
| if (margin_start_length.IsAuto()) |
| margin_start_length.SetValue(0); |
| if (margin_end_length.IsAuto()) |
| margin_end_length.SetValue(0); |
| } |
| |
| LayoutUnit margin_start_width = |
| MinimumValueForLength(margin_start_length, container_width); |
| LayoutUnit margin_end_width = |
| MinimumValueForLength(margin_end_length, container_width); |
| |
| LayoutUnit available_width = container_width; |
| if (AvoidsFloats() && containing_block->IsLayoutBlockFlow() && |
| ToLayoutBlockFlow(containing_block)->ContainsFloats()) { |
| available_width = ContainingBlockAvailableLineWidth(); |
| if (ShrinkToAvoidFloats() && available_width < container_width) { |
| margin_start = std::max(LayoutUnit(), margin_start_width); |
| margin_end = std::max(LayoutUnit(), margin_end_width); |
| } |
| } |
| |
| // CSS 2.1 (10.3.3): "If 'width' is not 'auto' and 'border-left-width' + |
| // 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any |
| // of 'margin-left' or 'margin-right' that are not 'auto') is larger than the |
| // width of the containing block, then any 'auto' values for 'margin-left' or |
| // 'margin-right' are, for the following rules, treated as zero. |
| LayoutUnit margin_box_width = |
| child_width + (!Style()->Width().IsAuto() |
| ? margin_start_width + margin_end_width |
| : LayoutUnit()); |
| |
| if (margin_box_width < available_width) { |
| // CSS 2.1: "If both 'margin-left' and 'margin-right' are 'auto', their used |
| // values are equal. This horizontally centers the element with respect to |
| // the edges of the containing block." |
| const ComputedStyle& containing_block_style = containing_block->StyleRef(); |
| if ((margin_start_length.IsAuto() && margin_end_length.IsAuto()) || |
| (!margin_start_length.IsAuto() && !margin_end_length.IsAuto() && |
| containing_block_style.GetTextAlign() == ETextAlign::kWebkitCenter)) { |
| // Other browsers center the margin box for align=center elements so we |
| // match them here. |
| LayoutUnit centered_margin_box_start = |
| std::max(LayoutUnit(), (available_width - child_width - |
| margin_start_width - margin_end_width) / |
| 2); |
| margin_start = centered_margin_box_start + margin_start_width; |
| margin_end = |
| available_width - child_width - margin_start + margin_end_width; |
| return; |
| } |
| |
| // Adjust margins for the align attribute |
| if ((!containing_block_style.IsLeftToRightDirection() && |
| containing_block_style.GetTextAlign() == ETextAlign::kWebkitLeft) || |
| (containing_block_style.IsLeftToRightDirection() && |
| containing_block_style.GetTextAlign() == ETextAlign::kWebkitRight)) { |
| if (containing_block_style.IsLeftToRightDirection() != |
| StyleRef().IsLeftToRightDirection()) { |
| if (!margin_start_length.IsAuto()) |
| margin_end_length = Length(kAuto); |
| } else { |
| if (!margin_end_length.IsAuto()) |
| margin_start_length = Length(kAuto); |
| } |
| } |
| |
| // CSS 2.1: "If there is exactly one value specified as 'auto', its used |
| // value follows from the equality." |
| if (margin_end_length.IsAuto()) { |
| margin_start = margin_start_width; |
| margin_end = available_width - child_width - margin_start; |
| return; |
| } |
| |
| if (margin_start_length.IsAuto()) { |
| margin_end = margin_end_width; |
| margin_start = available_width - child_width - margin_end; |
| return; |
| } |
| } |
| |
| // Either no auto margins, or our margin box width is >= the container width, |
| // auto margins will just turn into 0. |
| margin_start = margin_start_width; |
| margin_end = margin_end_width; |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBox::UpdateLogicalHeight() { |
| intrinsic_content_logical_height_ = ContentLogicalHeight(); |
| |
| LogicalExtentComputedValues computed_values; |
| ComputeLogicalHeight(computed_values); |
| |
| SetLogicalHeight(computed_values.extent_); |
| SetLogicalTop(computed_values.position_); |
| SetMarginBefore(computed_values.margins_.before_); |
| SetMarginAfter(computed_values.margins_.after_); |
| } |
| |
| static inline Length HeightForDocumentElement(const Document& document) { |
| return document.documentElement() |
| ->GetLayoutObject() |
| ->Style() |
| ->LogicalHeight(); |
| } |
| |
| void LayoutBox::ComputeLogicalHeight( |
| LogicalExtentComputedValues& computed_values) const { |
| LayoutUnit height = Style()->ContainsSize() ? BorderAndPaddingLogicalHeight() |
| : LogicalHeight(); |
| ComputeLogicalHeight(height, LogicalTop(), computed_values); |
| } |
| |
| void LayoutBox::ComputeLogicalHeight( |
| LayoutUnit logical_height, |
| LayoutUnit logical_top, |
| LogicalExtentComputedValues& computed_values) const { |
| computed_values.extent_ = logical_height; |
| computed_values.position_ = logical_top; |
| |
| // Cell height is managed by the table. |
| if (IsTableCell()) |
| return; |
| |
| Length h; |
| if (IsOutOfFlowPositioned()) { |
| ComputePositionedLogicalHeight(computed_values); |
| } else { |
| LayoutBlock* cb = ContainingBlock(); |
| |
| // If we are perpendicular to our containing block then we need to resolve |
| // our block-start and block-end margins so that if they are 'auto' we are |
| // centred or aligned within the inline flow containing block: this is done |
| // by computing the margins as though they are inline. |
| // Note that as this is the 'sizing phase' we are using our own writing mode |
| // rather than the containing block's. We use the containing block's writing |
| // mode when figuring out the block-direction margins for positioning in |
| // |computeAndSetBlockDirectionMargins| (i.e. margin collapsing etc.). |
| // http://www.w3.org/TR/2014/CR-css-writing-modes-3-20140320/#orthogonal-flows |
| MarginDirection flow_direction = |
| IsHorizontalWritingMode() != cb->IsHorizontalWritingMode() |
| ? kInlineDirection |
| : kBlockDirection; |
| |
| // For tables, calculate margins only. |
| if (IsTable()) { |
| ComputeMarginsForDirection( |
| flow_direction, cb, ContainingBlockLogicalWidthForContent(), |
| computed_values.extent_, computed_values.margins_.before_, |
| computed_values.margins_.after_, Style()->MarginBefore(), |
| Style()->MarginAfter()); |
| return; |
| } |
| |
| // FIXME: Account for writing-mode in flexible boxes. |
| // https://bugs.webkit.org/show_bug.cgi?id=46418 |
| bool in_horizontal_box = |
| Parent()->IsDeprecatedFlexibleBox() && |
| Parent()->Style()->BoxOrient() == EBoxOrient::kHorizontal; |
| bool stretching = Parent()->Style()->BoxAlign() == EBoxAlignment::kStretch; |
| bool treat_as_replaced = |
| ShouldComputeSizeAsReplaced() && (!in_horizontal_box || !stretching); |
| bool check_min_max_height = false; |
| |
| // The parent box is flexing us, so it has increased or decreased our |
| // height. We have to grab our cached flexible height. |
| if (HasOverrideLogicalContentHeight()) { |
| h = Length(OverrideLogicalContentHeight(), kFixed); |
| } else if (treat_as_replaced) { |
| h = Length(ComputeReplacedLogicalHeight(), kFixed); |
| } else { |
| h = Style()->LogicalHeight(); |
| check_min_max_height = true; |
| } |
| |
| // Block children of horizontal flexible boxes fill the height of the box. |
| // FIXME: Account for writing-mode in flexible boxes. |
| // https://bugs.webkit.org/show_bug.cgi?id=46418 |
| if (h.IsAuto() && in_horizontal_box && |
| ToLayoutDeprecatedFlexibleBox(Parent())->IsStretchingChildren()) { |
| h = Length(ParentBox()->ContentLogicalHeight() - MarginBefore() - |
| MarginAfter() - BorderAndPaddingLogicalHeight(), |
| kFixed); |
| check_min_max_height = false; |
| } |
| |
| LayoutUnit height_result; |
| if (check_min_max_height) { |
| height_result = ComputeLogicalHeightUsing( |
| kMainOrPreferredSize, Style()->LogicalHeight(), |
| computed_values.extent_ - BorderAndPaddingLogicalHeight()); |
| if (height_result == -1) |
| height_result = computed_values.extent_; |
| height_result = ConstrainLogicalHeightByMinMax( |
| height_result, |
| computed_values.extent_ - BorderAndPaddingLogicalHeight()); |
| } else { |
| // The only times we don't check min/max height are when a fixed length |
| // has been given as an override. Just use that. The value has already |
| // been adjusted for box-sizing. |
| DCHECK(h.IsFixed()); |
| height_result = LayoutUnit(h.Value()) + BorderAndPaddingLogicalHeight(); |
| } |
| |
| computed_values.extent_ = height_result; |
| ComputeMarginsForDirection( |
| flow_direction, cb, ContainingBlockLogicalWidthForContent(), |
| computed_values.extent_, computed_values.margins_.before_, |
| computed_values.margins_.after_, Style()->MarginBefore(), |
| Style()->MarginAfter()); |
| } |
| |
| // WinIE quirk: The <html> block always fills the entire canvas in quirks |
| // mode. The <body> always fills the <html> block in quirks mode. Only apply |
| // this quirk if the block is normal flow and no height is specified. When |
| // we're printing, we also need this quirk if the body or root has a |
| // percentage height since we don't set a height in LayoutView when we're |
| // printing. So without this quirk, the height has nothing to be a percentage |
| // of, and it ends up being 0. That is bad. |
| bool paginated_content_needs_base_height = |
| GetDocument().Printing() && h.IsPercentOrCalc() && |
| (IsDocumentElement() || |
| (IsBody() && |
| HeightForDocumentElement(GetDocument()).IsPercentOrCalc())) && |
| !IsInline(); |
| if (StretchesToViewport() || paginated_content_needs_base_height) { |
| LayoutUnit margins = CollapsedMarginBefore() + CollapsedMarginAfter(); |
| LayoutUnit visible_height = View()->ViewLogicalHeightForPercentages(); |
| if (IsDocumentElement()) { |
| computed_values.extent_ = |
| std::max(computed_values.extent_, visible_height - margins); |
| } else { |
| LayoutUnit margins_borders_padding = |
| margins + ParentBox()->MarginBefore() + ParentBox()->MarginAfter() + |
| ParentBox()->BorderAndPaddingLogicalHeight(); |
| computed_values.extent_ = std::max( |
| computed_values.extent_, visible_height - margins_borders_padding); |
| } |
| } |
| } |
| |
| LayoutUnit LayoutBox::ComputeLogicalHeightWithoutLayout() const { |
| // TODO(cbiesinger): We should probably return something other than just |
| // border + padding, but for now we have no good way to do anything else |
| // without layout, so we just use that. |
| LogicalExtentComputedValues computed_values; |
| ComputeLogicalHeight(BorderAndPaddingLogicalHeight(), LayoutUnit(), |
| computed_values); |
| return computed_values.extent_; |
| } |
| |
| LayoutUnit LayoutBox::ComputeLogicalHeightUsing( |
| SizeType height_type, |
| const Length& height, |
| LayoutUnit intrinsic_content_height) const { |
| LayoutUnit logical_height = ComputeContentAndScrollbarLogicalHeightUsing( |
| height_type, height, intrinsic_content_height); |
| if (logical_height != -1) { |
| if (height.IsSpecified()) |
| logical_height = AdjustBorderBoxLogicalHeightForBoxSizing(logical_height); |
| else |
| logical_height += BorderAndPaddingLogicalHeight(); |
| } |
| return logical_height; |
| } |
| |
| LayoutUnit LayoutBox::ComputeContentLogicalHeight( |
| SizeType height_type, |
| const Length& height, |
| LayoutUnit intrinsic_content_height) const { |
| LayoutUnit height_including_scrollbar = |
| ComputeContentAndScrollbarLogicalHeightUsing(height_type, height, |
| intrinsic_content_height); |
| if (height_including_scrollbar == -1) |
| return LayoutUnit(-1); |
| LayoutUnit adjusted = height_including_scrollbar; |
| if (height.IsSpecified()) { |
| // Keywords don't get adjusted for box-sizing |
| adjusted = |
| AdjustContentBoxLogicalHeightForBoxSizing(height_including_scrollbar); |
| } |
| return std::max(LayoutUnit(), adjusted - ScrollbarLogicalHeight()); |
| } |
| |
| LayoutUnit LayoutBox::ComputeIntrinsicLogicalContentHeightUsing( |
| const Length& logical_height_length, |
| LayoutUnit intrinsic_content_height, |
| LayoutUnit border_and_padding) const { |
| // FIXME(cbiesinger): The css-sizing spec is considering changing what |
| // min-content/max-content should resolve to. |
| // If that happens, this code will have to change. |
| if (logical_height_length.IsMinContent() || |
| logical_height_length.IsMaxContent() || |
| logical_height_length.IsFitContent()) { |
| if (IsAtomicInlineLevel()) |
| return IntrinsicSize().Height(); |
| return intrinsic_content_height; |
| } |
| if (logical_height_length.IsFillAvailable()) |
| return ContainingBlock()->AvailableLogicalHeight( |
| kExcludeMarginBorderPadding) - |
| border_and_padding; |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| LayoutUnit LayoutBox::ComputeContentAndScrollbarLogicalHeightUsing( |
| SizeType height_type, |
| const Length& height, |
| LayoutUnit intrinsic_content_height) const { |
| if (height.IsAuto()) |
| return height_type == kMinSize ? LayoutUnit() : LayoutUnit(-1); |
| // FIXME(cbiesinger): The css-sizing spec is considering changing what |
| // min-content/max-content should resolve to. |
| // If that happens, this code will have to change. |
| if (height.IsIntrinsic()) { |
| if (intrinsic_content_height == -1) |
| return LayoutUnit(-1); // Intrinsic height isn't available. |
| return ComputeIntrinsicLogicalContentHeightUsing( |
| height, intrinsic_content_height, |
| BorderAndPaddingLogicalHeight()) + |
| ScrollbarLogicalHeight(); |
| } |
| if (height.IsFixed()) |
| return LayoutUnit(height.Value()); |
| if (height.IsPercentOrCalc()) |
| return ComputePercentageLogicalHeight(height); |
| return LayoutUnit(-1); |
| } |
| |
| bool LayoutBox::StretchesToViewportInQuirksMode() const { |
| if (!IsDocumentElement() && !IsBody()) |
| return false; |
| return Style()->LogicalHeight().IsAuto() && |
| !IsFloatingOrOutOfFlowPositioned() && !IsInline() && |
| !FlowThreadContainingBlock(); |
| } |
| |
| bool LayoutBox::SkipContainingBlockForPercentHeightCalculation( |
| const LayoutBox* containing_block) const { |
| // If the writing mode of the containing block is orthogonal to ours, it means |
| // that we shouldn't skip anything, since we're going to resolve the |
| // percentage height against a containing block *width*. |
| if (IsHorizontalWritingMode() != containing_block->IsHorizontalWritingMode()) |
| return false; |
| |
| // Anonymous blocks should not impede percentage resolution on a child. |
| // Examples of such anonymous blocks are blocks wrapped around inlines that |
| // have block siblings (from the CSS spec) and multicol flow threads (an |
| // implementation detail). Another implementation detail, ruby runs, create |
| // anonymous inline-blocks, so skip those too. All other types of anonymous |
| // objects, such as table-cells, will be treated just as if they were |
| // non-anonymous. |
| if (containing_block->IsAnonymous()) { |
| EDisplay display = containing_block->StyleRef().Display(); |
| return display == EDisplay::kBlock || display == EDisplay::kInlineBlock; |
| } |
| |
| // For quirks mode, we skip most auto-height containing blocks when computing |
| // percentages. |
| return GetDocument().InQuirksMode() && !containing_block->IsTableCell() && |
| !containing_block->IsOutOfFlowPositioned() && |
| !containing_block->IsLayoutGrid() && |
| containing_block->Style()->LogicalHeight().IsAuto(); |
| } |
| |
| LayoutUnit LayoutBox::ComputePercentageLogicalHeight( |
| const Length& height) const { |
| LayoutBlock* cb = ContainingBlock(); |
| const LayoutBox* containing_block_child = this; |
| bool skipped_auto_height_containing_block = false; |
| LayoutUnit root_margin_border_padding_height; |
| while (!cb->IsLayoutView() && |
| SkipContainingBlockForPercentHeightCalculation(cb)) { |
| if (cb->IsBody() || cb->IsDocumentElement()) |
| root_margin_border_padding_height += cb->MarginBefore() + |
| cb->MarginAfter() + |
| cb->BorderAndPaddingLogicalHeight(); |
| skipped_auto_height_containing_block = true; |
| containing_block_child = cb; |
| cb = cb->ContainingBlock(); |
| } |
| cb->AddPercentHeightDescendant(const_cast<LayoutBox*>(this)); |
| |
| LayoutUnit available_height(-1); |
| if (IsHorizontalWritingMode() != cb->IsHorizontalWritingMode()) { |
| available_height = |
| containing_block_child->ContainingBlockLogicalWidthForContent(); |
| } else if (HasOverrideContainingBlockLogicalHeight()) { |
| available_height = OverrideContainingBlockContentLogicalHeight(); |
| } else if (cb->IsTableCell()) { |
| if (!skipped_auto_height_containing_block) { |
| // Table cells violate what the CSS spec says to do with heights. |
| // Basically we don't care if the cell specified a height or not. We just |
| // always make ourselves be a percentage of the cell's current content |
| // height. |
| if (!cb->HasOverrideLogicalContentHeight()) { |
| // https://drafts.csswg.org/css-tables-3/#row-layout: |
| // For the purpose of calculating [the minimum height of a row], |
| // descendants of table cells whose height depends on percentages |
| // of their parent cell's height are considered to have an auto |
| // height if they have overflow set to visible or hidden or if |
| // they are replaced elements, and a 0px height if they have not. |
| LayoutTableCell* cell = ToLayoutTableCell(cb); |
| if (Style()->OverflowY() != EOverflow::kVisible && |
| Style()->OverflowY() != EOverflow::kHidden && |
| !ShouldBeConsideredAsReplaced() && |
| (!cell->Style()->LogicalHeight().IsAuto() || |
| !cell->Table()->Style()->LogicalHeight().IsAuto())) |
| return LayoutUnit(); |
| return LayoutUnit(-1); |
| } |
| available_height = cb->OverrideLogicalContentHeight(); |
| } |
| } else { |
| available_height = cb->AvailableLogicalHeightForPercentageComputation(); |
| } |
| |
| if (available_height == -1) |
| return available_height; |
| |
| available_height -= root_margin_border_padding_height; |
| |
| if (IsTable() && IsOutOfFlowPositioned()) |
| available_height += cb->PaddingLogicalHeight(); |
| |
| LayoutUnit result = ValueForLength(height, available_height); |
| // |overrideLogicalContentHeight| is the maximum height made available by the |
| // cell to its percent height children when we decide they can determine the |
| // height of the cell. If the percent height child is box-sizing:content-box |
| // then we must subtract the border and padding from the cell's |
| // |availableHeight| (given by |overrideLogicalContentHeight|) to arrive |
| // at the child's computed height. |
| bool subtract_border_and_padding = |
| IsTable() || |
| (cb->IsTableCell() && !skipped_auto_height_containing_block && |
| cb->HasOverrideLogicalContentHeight() && |
| Style()->BoxSizing() == EBoxSizing::kContentBox); |
| if (subtract_border_and_padding) { |
| result -= BorderAndPaddingLogicalHeight(); |
| return std::max(LayoutUnit(), result); |
| } |
| return result; |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalWidth( |
| ShouldComputePreferred should_compute_preferred) const { |
| return ComputeReplacedLogicalWidthRespectingMinMaxWidth( |
| ComputeReplacedLogicalWidthUsing(kMainOrPreferredSize, |
| Style()->LogicalWidth()), |
| should_compute_preferred); |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalWidthRespectingMinMaxWidth( |
| LayoutUnit logical_width, |
| ShouldComputePreferred should_compute_preferred) const { |
| LayoutUnit min_logical_width = |
| (should_compute_preferred == kComputePreferred && |
| Style()->LogicalMinWidth().IsPercentOrCalc()) |
| ? logical_width |
| : ComputeReplacedLogicalWidthUsing(kMinSize, |
| Style()->LogicalMinWidth()); |
| LayoutUnit max_logical_width = |
| (should_compute_preferred == kComputePreferred && |
| Style()->LogicalMaxWidth().IsPercentOrCalc()) || |
| Style()->LogicalMaxWidth().IsMaxSizeNone() |
| ? logical_width |
| : ComputeReplacedLogicalWidthUsing(kMaxSize, |
| Style()->LogicalMaxWidth()); |
| return std::max(min_logical_width, |
| std::min(logical_width, max_logical_width)); |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalWidthUsing( |
| SizeType size_type, |
| const Length& logical_width) const { |
| DCHECK(size_type == kMinSize || size_type == kMainOrPreferredSize || |
| !logical_width.IsAuto()); |
| if (size_type == kMinSize && logical_width.IsAuto()) |
| return AdjustContentBoxLogicalWidthForBoxSizing(LayoutUnit()); |
| |
| switch (logical_width.GetType()) { |
| case kFixed: |
| return AdjustContentBoxLogicalWidthForBoxSizing(logical_width.Value()); |
| case kMinContent: |
| case kMaxContent: { |
| // MinContent/MaxContent don't need the availableLogicalWidth argument. |
| LayoutUnit available_logical_width; |
| return ComputeIntrinsicLogicalWidthUsing(logical_width, |
| available_logical_width, |
| BorderAndPaddingLogicalWidth()) - |
| BorderAndPaddingLogicalWidth(); |
| } |
| case kFitContent: |
| case kFillAvailable: |
| case kPercent: |
| case kCalculated: { |
| // FIXME: containingBlockLogicalWidthForContent() is wrong if the replaced |
| // element's writing-mode is perpendicular to the containing block's |
| // writing-mode. https://bugs.webkit.org/show_bug.cgi?id=46496 |
| const LayoutUnit cw = IsOutOfFlowPositioned() |
| ? ContainingBlockLogicalWidthForPositioned( |
| ToLayoutBoxModelObject(Container())) |
| : ContainingBlockLogicalWidthForContent(); |
| Length container_logical_width = |
| ContainingBlock()->Style()->LogicalWidth(); |
| // FIXME: Handle cases when containing block width is calculated or |
| // viewport percent. https://bugs.webkit.org/show_bug.cgi?id=91071 |
| if (logical_width.IsIntrinsic()) |
| return ComputeIntrinsicLogicalWidthUsing( |
| logical_width, cw, BorderAndPaddingLogicalWidth()) - |
| BorderAndPaddingLogicalWidth(); |
| if (cw > 0 || (!cw && (container_logical_width.IsFixed() || |
| container_logical_width.IsPercentOrCalc()))) |
| return AdjustContentBoxLogicalWidthForBoxSizing( |
| MinimumValueForLength(logical_width, cw)); |
| return LayoutUnit(); |
| } |
| case kAuto: |
| case kMaxSizeNone: |
| return IntrinsicLogicalWidth(); |
| case kExtendToZoom: |
| case kDeviceWidth: |
| case kDeviceHeight: |
| break; |
| } |
| |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalHeight(LayoutUnit) const { |
| return ComputeReplacedLogicalHeightRespectingMinMaxHeight( |
| ComputeReplacedLogicalHeightUsing(kMainOrPreferredSize, |
| Style()->LogicalHeight())); |
| } |
| |
| bool LayoutBox::LogicalHeightComputesAsNone(SizeType size_type) const { |
| DCHECK(size_type == kMinSize || size_type == kMaxSize); |
| Length logical_height = size_type == kMinSize ? Style()->LogicalMinHeight() |
| : Style()->LogicalMaxHeight(); |
| Length initial_logical_height = size_type == kMinSize |
| ? ComputedStyle::InitialMinHeight() |
| : ComputedStyle::InitialMaxHeight(); |
| |
| if (logical_height == initial_logical_height) |
| return true; |
| |
| if (LayoutBlock* cb = ContainingBlockForAutoHeightDetection(logical_height)) |
| return cb->HasAutoHeightOrContainingBlockWithAutoHeight(); |
| return false; |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalHeightRespectingMinMaxHeight( |
| LayoutUnit logical_height) const { |
| // If the height of the containing block is not specified explicitly (i.e., it |
| // depends on content height), and this element is not absolutely positioned, |
| // the percentage value is treated as '0' (for 'min-height') or 'none' (for |
| // 'max-height'). |
| LayoutUnit min_logical_height; |
| if (!LogicalHeightComputesAsNone(kMinSize)) |
| min_logical_height = ComputeReplacedLogicalHeightUsing( |
| kMinSize, Style()->LogicalMinHeight()); |
| LayoutUnit max_logical_height = logical_height; |
| if (!LogicalHeightComputesAsNone(kMaxSize)) |
| max_logical_height = ComputeReplacedLogicalHeightUsing( |
| kMaxSize, Style()->LogicalMaxHeight()); |
| return std::max(min_logical_height, |
| std::min(logical_height, max_logical_height)); |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalHeightUsing( |
| SizeType size_type, |
| const Length& logical_height) const { |
| DCHECK(size_type == kMinSize || size_type == kMainOrPreferredSize || |
| !logical_height.IsAuto()); |
| if (size_type == kMinSize && logical_height.IsAuto()) |
| return AdjustContentBoxLogicalHeightForBoxSizing(LayoutUnit()); |
| |
| switch (logical_height.GetType()) { |
| case kFixed: |
| return AdjustContentBoxLogicalHeightForBoxSizing(logical_height.Value()); |
| case kPercent: |
| case kCalculated: { |
| // TODO(rego): Check if we can somehow reuse |
| // LayoutBox::computePercentageLogicalHeight() and/or |
| // LayoutBlock::availableLogicalHeightForPercentageComputation() (see |
| // http://crbug.com/635655). |
| LayoutObject* cb = |
| IsOutOfFlowPositioned() ? Container() : ContainingBlock(); |
| while (cb->IsAnonymous()) |
| cb = cb->ContainingBlock(); |
| LayoutUnit stretched_height(-1); |
| if (cb->IsLayoutBlock()) { |
| LayoutBlock* block = ToLayoutBlock(cb); |
| block->AddPercentHeightDescendant(const_cast<LayoutBox*>(this)); |
| if (block->IsFlexItem()) |
| stretched_height = |
| ToLayoutFlexibleBox(block->Parent()) |
| ->ChildLogicalHeightForPercentageResolution(*block); |
| else if (block->IsGridItem() && |
| block->HasOverrideLogicalContentHeight()) |
| stretched_height = block->OverrideLogicalContentHeight(); |
| } |
| |
| if (cb->IsOutOfFlowPositioned() && cb->Style()->Height().IsAuto() && |
| !(cb->Style()->Top().IsAuto() || cb->Style()->Bottom().IsAuto())) { |
| SECURITY_DCHECK(cb->IsLayoutBlock()); |
| LayoutBlock* block = ToLayoutBlock(cb); |
| LogicalExtentComputedValues computed_values; |
| block->ComputeLogicalHeight(block->LogicalHeight(), LayoutUnit(), |
| computed_values); |
| LayoutUnit new_content_height = computed_values.extent_ - |
| block->BorderAndPaddingLogicalHeight() - |
| block->ScrollbarLogicalHeight(); |
| LayoutUnit new_height = |
| block->AdjustContentBoxLogicalHeightForBoxSizing( |
| new_content_height); |
| return AdjustContentBoxLogicalHeightForBoxSizing( |
| ValueForLength(logical_height, new_height)); |
| } |
| |
| // FIXME: availableLogicalHeight() is wrong if the replaced element's |
| // writing-mode is perpendicular to the containing block's writing-mode. |
| // https://bugs.webkit.org/show_bug.cgi?id=46496 |
| LayoutUnit available_height; |
| if (IsOutOfFlowPositioned()) { |
| available_height = ContainingBlockLogicalHeightForPositioned( |
| ToLayoutBoxModelObject(cb)); |
| } else if (stretched_height != -1) { |
| available_height = stretched_height; |
| } else if (HasOverrideContainingBlockLogicalHeight()) { |
| available_height = OverrideContainingBlockContentLogicalHeight(); |
| } else { |
| available_height = |
| ContainingBlockLogicalHeightForContent(kIncludeMarginBorderPadding); |
| // It is necessary to use the border-box to match WinIE's broken |
| // box model. This is essential for sizing inside |
| // table cells using percentage heights. |
| // FIXME: This needs to be made writing-mode-aware. If the cell and |
| // image are perpendicular writing-modes, this isn't right. |
| // https://bugs.webkit.org/show_bug.cgi?id=46997 |
| while (cb && !cb->IsLayoutView() && |
| (cb->Style()->LogicalHeight().IsAuto() || |
| cb->Style()->LogicalHeight().IsPercentOrCalc())) { |
| if (cb->IsTableCell()) { |
| // Don't let table cells squeeze percent-height replaced elements |
| // <http://bugs.webkit.org/show_bug.cgi?id=15359> |
| available_height = |
| std::max(available_height, IntrinsicLogicalHeight()); |
| return ValueForLength( |
| logical_height, |
| available_height - BorderAndPaddingLogicalHeight()); |
| } |
| ToLayoutBlock(cb)->AddPercentHeightDescendant( |
| const_cast<LayoutBox*>(this)); |
| cb = cb->ContainingBlock(); |
| } |
| } |
| return AdjustContentBoxLogicalHeightForBoxSizing( |
| ValueForLength(logical_height, available_height)); |
| } |
| case kMinContent: |
| case kMaxContent: |
| case kFitContent: |
| case kFillAvailable: |
| return AdjustContentBoxLogicalHeightForBoxSizing( |
| ComputeIntrinsicLogicalContentHeightUsing(logical_height, |
| IntrinsicLogicalHeight(), |
| BorderAndPaddingHeight())); |
| default: |
| return IntrinsicLogicalHeight(); |
| } |
| } |
| |
| LayoutUnit LayoutBox::AvailableLogicalHeight( |
| AvailableLogicalHeightType height_type) const { |
| // http://www.w3.org/TR/CSS2/visudet.html#propdef-height - We are interested |
| // in the content height. |
| // FIXME: Should we pass intrinsicContentLogicalHeight() instead of -1 here? |
| return ConstrainContentBoxLogicalHeightByMinMax( |
| AvailableLogicalHeightUsing(Style()->LogicalHeight(), height_type), |
| LayoutUnit(-1)); |
| } |
| |
| LayoutUnit LayoutBox::AvailableLogicalHeightUsing( |
| const Length& h, |
| AvailableLogicalHeightType height_type) const { |
| if (IsLayoutView()) { |
| return LayoutUnit( |
| IsHorizontalWritingMode() |
| ? ToLayoutView(this)->GetFrameView()->VisibleContentSize().Height() |
| : ToLayoutView(this)->GetFrameView()->VisibleContentSize().Width()); |
| } |
| |
| // We need to stop here, since we don't want to increase the height of the |
| // table artificially. We're going to rely on this cell getting expanded to |
| // some new height, and then when we lay out again we'll use the calculation |
| // below. |
| if (IsTableCell() && (h.IsAuto() || h.IsPercentOrCalc())) { |
| if (HasOverrideLogicalContentHeight()) |
| return OverrideLogicalContentHeight(); |
| return LogicalHeight() - BorderAndPaddingLogicalHeight(); |
| } |
| |
| if (IsFlexItem()) { |
| LayoutFlexibleBox& flex_box = ToLayoutFlexibleBox(*Parent()); |
| LayoutUnit stretched_height = |
| flex_box.ChildLogicalHeightForPercentageResolution(*this); |
| if (stretched_height != LayoutUnit(-1)) |
| return stretched_height; |
| } |
| |
| if (h.IsPercentOrCalc() && IsOutOfFlowPositioned()) { |
| // FIXME: This is wrong if the containingBlock has a perpendicular writing |
| // mode. |
| LayoutUnit available_height = |
| ContainingBlockLogicalHeightForPositioned(ContainingBlock()); |
| return AdjustContentBoxLogicalHeightForBoxSizing( |
| ValueForLength(h, available_height)); |
| } |
| |
| // FIXME: Should we pass intrinsicContentLogicalHeight() instead of -1 here? |
| LayoutUnit height_including_scrollbar = |
| ComputeContentAndScrollbarLogicalHeightUsing(kMainOrPreferredSize, h, |
| LayoutUnit(-1)); |
| if (height_including_scrollbar != -1) |
| return std::max(LayoutUnit(), AdjustContentBoxLogicalHeightForBoxSizing( |
| height_including_scrollbar) - |
| ScrollbarLogicalHeight()); |
| |
| // FIXME: Check logicalTop/logicalBottom here to correctly handle vertical |
| // writing-mode. |
| // https://bugs.webkit.org/show_bug.cgi?id=46500 |
| if (IsLayoutBlock() && IsOutOfFlowPositioned() && |
| Style()->Height().IsAuto() && |
| !(Style()->Top().IsAuto() || Style()->Bottom().IsAuto())) { |
| LayoutBlock* block = const_cast<LayoutBlock*>(ToLayoutBlock(this)); |
| LogicalExtentComputedValues computed_values; |
| block->ComputeLogicalHeight(block->LogicalHeight(), LayoutUnit(), |
| computed_values); |
| LayoutUnit new_content_height = computed_values.extent_ - |
| block->BorderAndPaddingLogicalHeight() - |
| block->ScrollbarLogicalHeight(); |
| return AdjustContentBoxLogicalHeightForBoxSizing(new_content_height); |
| } |
| |
| // FIXME: This is wrong if the containingBlock has a perpendicular writing |
| // mode. |
| LayoutUnit available_height = |
| ContainingBlockLogicalHeightForContent(height_type); |
| if (height_type == kExcludeMarginBorderPadding) { |
| // FIXME: Margin collapsing hasn't happened yet, so this incorrectly removes |
| // collapsed margins. |
| available_height -= |
| MarginBefore() + MarginAfter() + BorderAndPaddingLogicalHeight(); |
| } |
| return available_height; |
| } |
| |
| void LayoutBox::ComputeAndSetBlockDirectionMargins( |
| const LayoutBlock* containing_block) { |
| LayoutUnit margin_before; |
| LayoutUnit margin_after; |
| DCHECK(containing_block); |
| ComputeMarginsForDirection( |
| kBlockDirection, containing_block, |
| ContainingBlockLogicalWidthForContent(), LogicalHeight(), margin_before, |
| margin_after, StyleRef().MarginBeforeUsing(containing_block->StyleRef()), |
| StyleRef().MarginAfterUsing(containing_block->StyleRef())); |
| // Note that in this 'positioning phase' of the layout we are using the |
| // containing block's writing mode rather than our own when calculating |
| // margins. |
| // http://www.w3.org/TR/2014/CR-css-writing-modes-3-20140320/#orthogonal-flows |
| containing_block->SetMarginBeforeForChild(*this, margin_before); |
| containing_block->SetMarginAfterForChild(*this, margin_after); |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockLogicalWidthForPositioned( |
| const LayoutBoxModelObject* containing_block, |
| bool check_for_perpendicular_writing_mode) const { |
| if (check_for_perpendicular_writing_mode && |
| containing_block->IsHorizontalWritingMode() != IsHorizontalWritingMode()) |
| return ContainingBlockLogicalHeightForPositioned(containing_block, false); |
| |
| // Use viewport as container for top-level fixed-position elements. |
| if (Style()->GetPosition() == EPosition::kFixed && |
| containing_block->IsLayoutView() && !GetDocument().Printing()) { |
| const LayoutView* view = ToLayoutView(containing_block); |
| if (LocalFrameView* frame_view = view->GetFrameView()) { |
| // Don't use visibleContentRect since the PaintLayer's size has not been |
| // set yet. |
| LayoutSize viewport_size( |
| frame_view->LayoutViewportScrollableArea()->ExcludeScrollbars( |
| frame_view->FrameRect().Size())); |
| return LayoutUnit(containing_block->IsHorizontalWritingMode() |
| ? viewport_size.Width() |
| : viewport_size.Height()); |
| } |
| } |
| |
| if (HasOverrideContainingBlockLogicalWidth()) |
| return OverrideContainingBlockContentLogicalWidth(); |
| |
| // Ensure we compute our width based on the width of our rel-pos inline |
| // container rather than any anonymous block created to manage a block-flow |
| // ancestor of ours in the rel-pos inline's inline flow. |
| if (containing_block->IsAnonymousBlock() && |
| containing_block->IsRelPositioned()) |
| containing_block = ToLayoutBox(containing_block)->Continuation(); |
| else if (containing_block->IsBox()) |
| return std::max(LayoutUnit(), |
| ToLayoutBox(containing_block)->ClientLogicalWidth()); |
| |
| DCHECK(containing_block->IsLayoutInline()); |
| DCHECK(containing_block->IsInFlowPositioned()); |
| |
| const LayoutInline* flow = ToLayoutInline(containing_block); |
| InlineFlowBox* first = flow->FirstLineBox(); |
| InlineFlowBox* last = flow->LastLineBox(); |
| |
| // If the containing block is empty, return a width of 0. |
| if (!first || !last) |
| return LayoutUnit(); |
| |
| LayoutUnit from_left; |
| LayoutUnit from_right; |
| if (containing_block->Style()->IsLeftToRightDirection()) { |
| from_left = first->LogicalLeft() + first->BorderLogicalLeft(); |
| from_right = |
| last->LogicalLeft() + last->LogicalWidth() - last->BorderLogicalRight(); |
| } else { |
| from_right = first->LogicalLeft() + first->LogicalWidth() - |
| first->BorderLogicalRight(); |
| from_left = last->LogicalLeft() + last->BorderLogicalLeft(); |
| } |
| |
| return std::max(LayoutUnit(), from_right - from_left); |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockLogicalHeightForPositioned( |
| const LayoutBoxModelObject* containing_block, |
| bool check_for_perpendicular_writing_mode) const { |
| if (check_for_perpendicular_writing_mode && |
| containing_block->IsHorizontalWritingMode() != IsHorizontalWritingMode()) |
| return ContainingBlockLogicalWidthForPositioned(containing_block, false); |
| |
| // Use viewport as container for top-level fixed-position elements. |
| if (Style()->GetPosition() == EPosition::kFixed && |
| containing_block->IsLayoutView() && !GetDocument().Printing()) { |
| const LayoutView* view = ToLayoutView(containing_block); |
| if (LocalFrameView* frame_view = view->GetFrameView()) { |
| // Don't use visibleContentRect since the PaintLayer's size has not been |
| // set yet. |
| LayoutSize viewport_size( |
| frame_view->LayoutViewportScrollableArea()->ExcludeScrollbars( |
| frame_view->FrameRect().Size())); |
| return containing_block->IsHorizontalWritingMode() |
| ? viewport_size.Height() |
| : viewport_size.Width(); |
| } |
| } |
| |
| if (HasOverrideContainingBlockLogicalHeight()) |
| return OverrideContainingBlockContentLogicalHeight(); |
| |
| if (containing_block->IsBox()) { |
| const LayoutBlock* cb = containing_block->IsLayoutBlock() |
| ? ToLayoutBlock(containing_block) |
| : containing_block->ContainingBlock(); |
| return cb->ClientLogicalHeight(); |
| } |
| |
| DCHECK(containing_block->IsLayoutInline()); |
| DCHECK(containing_block->IsInFlowPositioned()); |
| |
| const LayoutInline* flow = ToLayoutInline(containing_block); |
| InlineFlowBox* first = flow->FirstLineBox(); |
| InlineFlowBox* last = flow->LastLineBox(); |
| |
| // If the containing block is empty, return a height of 0. |
| if (!first || !last) |
| return LayoutUnit(); |
| |
| LayoutUnit height_result; |
| LayoutRect bounding_box(flow->LinesBoundingBox()); |
| if (containing_block->IsHorizontalWritingMode()) |
| height_result = bounding_box.Height(); |
| else |
| height_result = bounding_box.Width(); |
| height_result -= |
| (containing_block->BorderBefore() + containing_block->BorderAfter()); |
| return height_result; |
| } |
| |
| static LayoutUnit AccumulateStaticOffsetForFlowThread( |
| LayoutBox& layout_box, |
| LayoutUnit inline_position, |
| LayoutUnit& block_position) { |
| if (layout_box.IsTableRow()) |
| return LayoutUnit(); |
| block_position += layout_box.LogicalTop(); |
| if (!layout_box.IsLayoutFlowThread()) |
| return LayoutUnit(); |
| LayoutUnit previous_inline_position = inline_position; |
| // We're walking out of a flowthread here. This flow thread is not in the |
| // containing block chain, so we need to convert the position from the |
| // coordinate space of this flowthread to the containing coordinate space. |
| ToLayoutFlowThread(layout_box) |
| .FlowThreadToContainingCoordinateSpace(block_position, inline_position); |
| return inline_position - previous_inline_position; |
| } |
| |
| void LayoutBox::ComputeInlineStaticDistance( |
| Length& logical_left, |
| Length& logical_right, |
| const LayoutBox* child, |
| const LayoutBoxModelObject* container_block, |
| LayoutUnit container_logical_width) { |
| if (!logical_left.IsAuto() || !logical_right.IsAuto()) |
| return; |
| |
| LayoutObject* parent = child->Parent(); |
| TextDirection parent_direction = parent->Style()->Direction(); |
| |
| // This method is using EnclosingBox() which is wrong for absolutely |
| // positioned grid items, as they rely on the grid area. So for grid items if |
| // both "left" and "right" properties are "auto", we can consider that one of |
| // them (depending on the direction) is simply "0". |
| if (parent->IsLayoutGrid() && parent == child->ContainingBlock()) { |
| if (parent_direction == TextDirection::kLtr) |
| logical_left.SetValue(kFixed, 0); |
| else |
| logical_right.SetValue(kFixed, 0); |
| return; |
| } |
| |
| // For multicol we also need to keep track of the block position, since that |
| // determines which column we're in and thus affects the inline position. |
| LayoutUnit static_block_position = child->Layer()->StaticBlockPosition(); |
| |
| // FIXME: The static distance computation has not been patched for mixed |
| // writing modes yet. |
| if (parent_direction == TextDirection::kLtr) { |
| LayoutUnit static_position = child->Layer()->StaticInlinePosition() - |
| container_block->BorderLogicalLeft(); |
| for (LayoutObject* curr = child->Parent(); curr && curr != container_block; |
| curr = curr->Container()) { |
| if (curr->IsBox()) { |
| static_position += ToLayoutBox(curr)->LogicalLeft(); |
| if (ToLayoutBox(curr)->IsInFlowPositioned()) |
| static_position += |
| ToLayoutBox(curr)->OffsetForInFlowPosition().Width(); |
| if (curr->IsInsideFlowThread()) |
| static_position += AccumulateStaticOffsetForFlowThread( |
| *ToLayoutBox(curr), static_position, static_block_position); |
| } else if (curr->IsInline()) { |
| if (curr->IsInFlowPositioned()) { |
| if (!curr->Style()->LogicalLeft().IsAuto()) |
| static_position += |
| ValueForLength(curr->Style()->LogicalLeft(), |
| curr->ContainingBlock()->AvailableWidth()); |
| else |
| static_position -= |
| ValueForLength(curr->Style()->LogicalRight(), |
| curr->ContainingBlock()->AvailableWidth()); |
| } |
| } |
| } |
| logical_left.SetValue(kFixed, static_position); |
| } else { |
| LayoutBox* enclosing_box = child->Parent()->EnclosingBox(); |
| LayoutUnit static_position = child->Layer()->StaticInlinePosition() + |
| container_logical_width + |
| container_block->BorderLogicalLeft(); |
| for (LayoutObject* curr = child->Parent(); curr; curr = curr->Container()) { |
| if (curr->IsBox()) { |
| if (curr == enclosing_box) |
| static_position -= enclosing_box->LogicalWidth(); |
| if (curr != container_block) { |
| static_position -= ToLayoutBox(curr)->LogicalLeft(); |
| if (ToLayoutBox(curr)->IsInFlowPositioned()) |
| static_position -= |
| ToLayoutBox(curr)->OffsetForInFlowPosition().Width(); |
| if (curr->IsInsideFlowThread()) |
| static_position -= AccumulateStaticOffsetForFlowThread( |
| *ToLayoutBox(curr), static_position, static_block_position); |
| } |
| } else if (curr->IsInline()) { |
| if (curr->IsInFlowPositioned()) { |
| if (!curr->Style()->LogicalLeft().IsAuto()) |
| static_position -= |
| ValueForLength(curr->Style()->LogicalLeft(), |
| curr->ContainingBlock()->AvailableWidth()); |
| else |
| static_position += |
| ValueForLength(curr->Style()->LogicalRight(), |
| curr->ContainingBlock()->AvailableWidth()); |
| } |
| } |
| if (curr == container_block) |
| break; |
| } |
| logical_right.SetValue(kFixed, static_position); |
| } |
| } |
| |
| void LayoutBox::ComputePositionedLogicalWidth( |
| LogicalExtentComputedValues& computed_values) const { |
| // QUESTIONS |
| // FIXME 1: Should we still deal with these the cases of 'left' or 'right' |
| // having the type 'static' in determining whether to calculate the static |
| // distance? |
| // NOTE: 'static' is not a legal value for 'left' or 'right' as of CSS 2.1. |
| |
| // FIXME 2: Can perhaps optimize out cases when max-width/min-width are |
| // greater than or less than the computed width(). Be careful of box-sizing |
| // and percentage issues. |
| |
| // The following is based off of the W3C Working Draft from April 11, 2006 of |
| // CSS 2.1: Section 10.3.7 "Absolutely positioned, non-replaced elements" |
| // <http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width> |
| // (block-style-comments in this function and in |
| // computePositionedLogicalWidthUsing() correspond to text from the spec) |
| |
| // We don't use containingBlock(), since we may be positioned by an enclosing |
| // relative positioned inline. |
| const LayoutBoxModelObject* container_block = |
| ToLayoutBoxModelObject(Container()); |
| |
| const LayoutUnit container_logical_width = |
| ContainingBlockLogicalWidthForPositioned(container_block); |
| |
| // Use the container block's direction except when calculating the static |
| // distance. This conforms with the reference results for |
| // abspos-replaced-width-margin-000.htm of the CSS 2.1 test suite. |
| TextDirection container_direction = container_block->Style()->Direction(); |
| |
| bool is_horizontal = IsHorizontalWritingMode(); |
| const LayoutUnit borders_plus_padding = BorderAndPaddingLogicalWidth(); |
| const Length margin_logical_left = |
| is_horizontal ? Style()->MarginLeft() : Style()->MarginTop(); |
| const Length margin_logical_right = |
| is_horizontal ? Style()->MarginRight() : Style()->MarginBottom(); |
| |
| Length logical_left_length = Style()->LogicalLeft(); |
| Length logical_right_length = Style()->LogicalRight(); |
| // --------------------------------------------------------------------------- |
| // For the purposes of this section and the next, the term "static position" |
| // (of an element) refers, roughly, to the position an element would have had |
| // in the normal flow. More precisely: |
| // |
| // * The static position for 'left' is the distance from the left edge of the |
| // containing block to the left margin edge of a hypothetical box that |
| // would have been the first box of the element if its 'position' property |
| // had been 'static' and 'float' had been 'none'. The value is negative if |
| // the hypothetical box is to the left of the containing block. |
| // * The static position for 'right' is the distance from the right edge of |
| // the containing block to the right margin edge of the same hypothetical |
| // box as above. The value is positive if the hypothetical box is to the |
| // left of the containing block's edge. |
| // |
| // But rather than actually calculating the dimensions of that hypothetical |
| // box, user agents are free to make a guess at its probable position. |
| // |
| // For the purposes of calculating the static position, the containing block |
| // of fixed positioned elements is the initial containing block instead of |
| // the viewport, and all scrollable boxes should be assumed to be scrolled to |
| // their origin. |
| // --------------------------------------------------------------------------- |
| // see FIXME 1 |
| // Calculate the static distance if needed. |
| ComputeInlineStaticDistance(logical_left_length, logical_right_length, this, |
| container_block, container_logical_width); |
| |
| // Calculate constraint equation values for 'width' case. |
| ComputePositionedLogicalWidthUsing( |
| kMainOrPreferredSize, Style()->LogicalWidth(), container_block, |
| container_direction, container_logical_width, borders_plus_padding, |
| logical_left_length, logical_right_length, margin_logical_left, |
| margin_logical_right, computed_values); |
| |
| // Calculate constraint equation values for 'max-width' case. |
| if (!Style()->LogicalMaxWidth().IsMaxSizeNone()) { |
| LogicalExtentComputedValues max_values; |
| |
| ComputePositionedLogicalWidthUsing( |
| kMaxSize, Style()->LogicalMaxWidth(), container_block, |
| container_direction, container_logical_width, borders_plus_padding, |
| logical_left_length, logical_right_length, margin_logical_left, |
| margin_logical_right, max_values); |
| |
| if (computed_values.extent_ > max_values.extent_) { |
| computed_values.extent_ = max_values.extent_; |
| computed_values.position_ = max_values.position_; |
| computed_values.margins_.start_ = max_values.margins_.start_; |
| computed_values.margins_.end_ = max_values.margins_.end_; |
| } |
| } |
| |
| // Calculate constraint equation values for 'min-width' case. |
| if (!Style()->LogicalMinWidth().IsZero() || |
| Style()->LogicalMinWidth().IsIntrinsic()) { |
| LogicalExtentComputedValues min_values; |
| |
| ComputePositionedLogicalWidthUsing( |
| kMinSize, Style()->LogicalMinWidth(), container_block, |
| container_direction, container_logical_width, borders_plus_padding, |
| logical_left_length, logical_right_length, margin_logical_left, |
| margin_logical_right, min_values); |
| |
| if (computed_values.extent_ < min_values.extent_) { |
| computed_values.extent_ = min_values.extent_; |
| computed_values.position_ = min_values.position_; |
| computed_values.margins_.start_ = min_values.margins_.start_; |
| computed_values.margins_.end_ = min_values.margins_.end_; |
| } |
| } |
| |
| computed_values.extent_ += borders_plus_padding; |
| } |
| |
| void LayoutBox::ComputeLogicalLeftPositionedOffset( |
| LayoutUnit& logical_left_pos, |
| const LayoutBox* child, |
| LayoutUnit logical_width_value, |
| const LayoutBoxModelObject* container_block, |
| LayoutUnit container_logical_width) { |
| // Deal with differing writing modes here. Our offset needs to be in the |
| // containing block's coordinate space. If the containing block is flipped |
| // along this axis, then we need to flip the coordinate. This can only happen |
| // if the containing block is both a flipped mode and perpendicular to us. |
| if (container_block->IsHorizontalWritingMode() != |
| child->IsHorizontalWritingMode() && |
| container_block->Style()->IsFlippedBlocksWritingMode()) { |
| logical_left_pos = |
| container_logical_width - logical_width_value - logical_left_pos; |
| logical_left_pos += |
| (child->IsHorizontalWritingMode() ? container_block->BorderRight() |
| : container_block->BorderBottom()); |
| } else { |
| logical_left_pos += |
| (child->IsHorizontalWritingMode() ? container_block->BorderLeft() |
| : container_block->BorderTop()); |
| } |
| } |
| |
| LayoutUnit LayoutBox::ShrinkToFitLogicalWidth( |
| LayoutUnit available_logical_width, |
| LayoutUnit borders_plus_padding) const { |
| LayoutUnit preferred_logical_width = |
| MaxPreferredLogicalWidth() - borders_plus_padding; |
| LayoutUnit preferred_min_logical_width = |
| MinPreferredLogicalWidth() - borders_plus_padding; |
| return std::min( |
| std::max(preferred_min_logical_width, available_logical_width), |
| preferred_logical_width); |
| } |
| |
| void LayoutBox::ComputePositionedLogicalWidthUsing( |
| SizeType width_size_type, |
| Length logical_width, |
| const LayoutBoxModelObject* container_block, |
| TextDirection container_direction, |
| LayoutUnit container_logical_width, |
| LayoutUnit borders_plus_padding, |
| const Length& logical_left, |
| const Length& logical_right, |
| const Length& margin_logical_left, |
| const Length& margin_logical_right, |
| LogicalExtentComputedValues& computed_values) const { |
| LayoutUnit logical_width_value; |
| |
| DCHECK(width_size_type == kMinSize || |
| width_size_type == kMainOrPreferredSize || !logical_width.IsAuto()); |
| if (width_size_type == kMinSize && logical_width.IsAuto()) |
| logical_width_value = LayoutUnit(); |
| else if (logical_width.IsIntrinsic()) |
| logical_width_value = |
| ComputeIntrinsicLogicalWidthUsing( |
| logical_width, container_logical_width, borders_plus_padding) - |
| borders_plus_padding; |
| else |
| logical_width_value = AdjustContentBoxLogicalWidthForBoxSizing( |
| ValueForLength(logical_width, container_logical_width)); |
| |
| // 'left' and 'right' cannot both be 'auto' because one would of been |
| // converted to the static position already |
| DCHECK(!(logical_left.IsAuto() && logical_right.IsAuto())); |
| |
| // minimumValueForLength will convert 'auto' to 0 so that it doesn't impact |
| // the available space computation below. |
| LayoutUnit logical_left_value = |
| MinimumValueForLength(logical_left, container_logical_width); |
| LayoutUnit logical_right_value = |
| MinimumValueForLength(logical_right, container_logical_width); |
| |
| const LayoutUnit container_relative_logical_width = |
| ContainingBlockLogicalWidthForPositioned(container_block, false); |
| |
| bool logical_width_is_auto = logical_width.IsAuto(); |
| bool logical_left_is_auto = logical_left.IsAuto(); |
| bool logical_right_is_auto = logical_right.IsAuto(); |
| LayoutUnit& margin_logical_left_value = Style()->IsLeftToRightDirection() |
| ? computed_values.margins_.start_ |
| : computed_values.margins_.end_; |
| LayoutUnit& margin_logical_right_value = |
| Style()->IsLeftToRightDirection() ? computed_values.margins_.end_ |
| : computed_values.margins_.start_; |
| if (!logical_left_is_auto && !logical_width_is_auto && |
| !logical_right_is_auto) { |
| // ------------------------------------------------------------------------- |
| // If none of the three is 'auto': If both 'margin-left' and 'margin- |
| // right' are 'auto', solve the equation under the extra constraint that |
| // the two margins get equal values, unless this would make them negative, |
| // in which case when direction of the containing block is 'ltr' ('rtl'), |
| // set 'margin-left' ('margin-right') to zero and solve for 'margin-right' |
| // ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', |
| // solve the equation for that value. If the values are over-constrained, |
| // ignore the value for 'left' (in case the 'direction' property of the |
| // containing block is 'rtl') or 'right' (in case 'direction' is 'ltr') |
| // and solve for that value. |
| // ------------------------------------------------------------------------- |
| // NOTE: It is not necessary to solve for 'right' in the over constrained |
| // case because the value is not used for any further calculations. |
| |
| computed_values.extent_ = logical_width_value; |
| |
| const LayoutUnit available_space = |
| container_logical_width - |
| (logical_left_value + computed_values.extent_ + logical_right_value + |
| borders_plus_padding); |
| |
| // Margins are now the only unknown |
| if (margin_logical_left.IsAuto() && margin_logical_right.IsAuto()) { |
| // Both margins auto, solve for equality |
| if (available_space >= 0) { |
| margin_logical_left_value = |
| available_space / 2; // split the difference |
| margin_logical_right_value = |
| available_space - |
| margin_logical_left_value; // account for odd valued differences |
| } else { |
| // Use the containing block's direction rather than the parent block's |
| // per CSS 2.1 reference test abspos-non-replaced-width-margin-000. |
| if (container_direction == TextDirection::kLtr) { |
| margin_logical_left_value = LayoutUnit(); |
| margin_logical_right_value = available_space; // will be negative |
| } else { |
| margin_logical_left_value = available_space; // will be negative |
| margin_logical_right_value = LayoutUnit(); |
| } |
| } |
| } else if (margin_logical_left.IsAuto()) { |
| // Solve for left margin |
| margin_logical_right_value = ValueForLength( |
| margin_logical_right, container_relative_logical_width); |
| margin_logical_left_value = available_space - margin_logical_right_value; |
| } else if (margin_logical_right.IsAuto()) { |
| // Solve for right margin |
| margin_logical_left_value = |
| ValueForLength(margin_logical_left, container_relative_logical_width); |
| margin_logical_right_value = available_space - margin_logical_left_value; |
| } else { |
| // Over-constrained, solve for left if direction is RTL |
| margin_logical_left_value = |
| ValueForLength(margin_logical_left, container_relative_logical_width); |
| margin_logical_right_value = ValueForLength( |
| margin_logical_right, container_relative_logical_width); |
| |
| // Use the containing block's direction rather than the parent block's |
| // per CSS 2.1 reference test abspos-non-replaced-width-margin-000. |
| if (container_direction == TextDirection::kRtl) |
| logical_left_value = (available_space + logical_left_value) - |
| margin_logical_left_value - |
| margin_logical_right_value; |
| } |
| } else { |
| // ------------------------------------------------------------------------- |
| // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' |
| // to 0, and pick the one of the following six rules that applies. |
| // |
| // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the |
| // width is shrink-to-fit. Then solve for 'left' |
| // |
| // OMIT RULE 2 AS IT SHOULD NEVER BE HIT |
| // ------------------------------------------------------------------ |
| // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if |
| // the 'direction' property of the containing block is 'ltr' set |
| // 'left' to the static position, otherwise set 'right' to the |
| // static position. Then solve for 'left' (if 'direction is 'rtl') |
| // or 'right' (if 'direction' is 'ltr'). |
| // ------------------------------------------------------------------ |
| // |
| // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the |
| // width is shrink-to-fit . Then solve for 'right' |
| // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve |
| // for 'left' |
| // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve |
| // for 'width' |
| // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve |
| // for 'right' |
| // |
| // Calculation of the shrink-to-fit width is similar to calculating the |
| // width of a table cell using the automatic table layout algorithm. |
| // Roughly: calculate the preferred width by formatting the content without |
| // breaking lines other than where explicit line breaks occur, and also |
| // calculate the preferred minimum width, e.g., by trying all possible line |
| // breaks. CSS 2.1 does not define the exact algorithm. |
| // Thirdly, calculate the available width: this is found by solving for |
| // 'width' after setting 'left' (in case 1) or 'right' (in case 3) to 0. |
| // |
| // Then the shrink-to-fit width is: |
| // min(max(preferred minimum width, available width), preferred width). |
| // ------------------------------------------------------------------------- |
| // NOTE: For rules 3 and 6 it is not necessary to solve for 'right' |
| // because the value is not used for any further calculations. |
| |
| // Calculate margins, 'auto' margins are ignored. |
| margin_logical_left_value = MinimumValueForLength( |
| margin_logical_left, container_relative_logical_width); |
| margin_logical_right_value = MinimumValueForLength( |
| margin_logical_right, container_relative_logical_width); |
| |
| const LayoutUnit available_space = |
| container_logical_width - |
| (margin_logical_left_value + margin_logical_right_value + |
| logical_left_value + logical_right_value + borders_plus_padding); |
| |
| // FIXME: Is there a faster way to find the correct case? |
| // Use rule/case that applies. |
| if (logical_left_is_auto && logical_width_is_auto && |
| !logical_right_is_auto) { |
| // RULE 1: (use shrink-to-fit for width, and solve of left) |
| computed_values.extent_ = |
| ShrinkToFitLogicalWidth(available_space, borders_plus_padding); |
| logical_left_value = available_space - computed_values.extent_; |
| } else if (!logical_left_is_auto && logical_width_is_auto && |
| logical_right_is_auto) { |
| // RULE 3: (use shrink-to-fit for width, and no need solve of right) |
| computed_values.extent_ = |
| ShrinkToFitLogicalWidth(available_space, borders_plus_padding); |
| } else if (logical_left_is_auto && !logical_width_is_auto && |
| !logical_right_is_auto) { |
| // RULE 4: (solve for left) |
| computed_values.extent_ = logical_width_value; |
| logical_left_value = available_space - computed_values.extent_; |
| } else if (!logical_left_is_auto && logical_width_is_auto && |
| !logical_right_is_auto) { |
| // RULE 5: (solve for width) |
| if (AutoWidthShouldFitContent()) |
| computed_values.extent_ = |
| ShrinkToFitLogicalWidth(available_space, borders_plus_padding); |
| else |
| computed_values.extent_ = std::max(LayoutUnit(), available_space); |
| } else if (!logical_left_is_auto && !logical_width_is_auto && |
| logical_right_is_auto) { |
| // RULE 6: (no need solve for right) |
| computed_values.extent_ = logical_width_value; |
| } |
| } |
| |
| // Use computed values to calculate the horizontal position. |
| |
| // FIXME: This hack is needed to calculate the logical left position for a |
| // 'rtl' relatively positioned, inline because right now, it is using the |
| // logical left position of the first line box when really it should use the |
| // last line box. When this is fixed elsewhere, this block should be removed. |
| if (container_block->IsLayoutInline() && |
| !container_block->Style()->IsLeftToRightDirection()) { |
| const LayoutInline* flow = ToLayoutInline(container_block); |
| InlineFlowBox* first_line = flow->FirstLineBox(); |
| InlineFlowBox* last_line = flow->LastLineBox(); |
| if (first_line && last_line && first_line != last_line) { |
| computed_values.position_ = |
| logical_left_value + margin_logical_left_value + |
| last_line->BorderLogicalLeft() + |
| (last_line->LogicalLeft() - first_line->LogicalLeft()); |
| return; |
| } |
| } |
| |
| if (container_block->IsBox() && |
| ToLayoutBox(container_block)->ScrollsOverflowY() && |
| ToLayoutBox(container_block) |
| ->ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { |
| logical_left_value = logical_left_value + |
| ToLayoutBox(container_block)->VerticalScrollbarWidth(); |
| } |
| |
| computed_values.position_ = logical_left_value + margin_logical_left_value; |
| ComputeLogicalLeftPositionedOffset(computed_values.position_, this, |
| computed_values.extent_, container_block, |
| container_logical_width); |
| } |
| |
| void LayoutBox::ComputeBlockStaticDistance( |
| Length& logical_top, |
| Length& logical_bottom, |
| const LayoutBox* child, |
| const LayoutBoxModelObject* container_block) { |
| if (!logical_top.IsAuto() || !logical_bottom.IsAuto()) |
| return; |
| |
| // FIXME: The static distance computation has not been patched for mixed |
| // writing modes. |
| LayoutUnit static_logical_top = child->Layer()->StaticBlockPosition(); |
| for (LayoutObject* curr = child->Parent(); curr && curr != container_block; |
| curr = curr->Container()) { |
| if (!curr->IsBox() || curr->IsTableRow()) |
| continue; |
| const LayoutBox& box = *ToLayoutBox(curr); |
| static_logical_top += box.LogicalTop(); |
| if (box.IsInFlowPositioned()) |
| static_logical_top += box.OffsetForInFlowPosition().Height(); |
| if (!box.IsLayoutFlowThread()) |
| continue; |
| // We're walking out of a flowthread here. This flow thread is not in the |
| // containing block chain, so we need to convert the position from the |
| // coordinate space of this flowthread to the containing coordinate space. |
| // The inline position cannot affect the block position, so we don't bother |
| // calculating it. |
| LayoutUnit dummy_inline_position; |
| ToLayoutFlowThread(box).FlowThreadToContainingCoordinateSpace( |
| static_logical_top, dummy_inline_position); |
| } |
| logical_top.SetValue(kFixed, |
| static_logical_top - container_block->BorderBefore()); |
| } |
| |
| void LayoutBox::ComputePositionedLogicalHeight( |
| LogicalExtentComputedValues& computed_values) const { |
| // The following is based off of the W3C Working Draft from April 11, 2006 of |
| // CSS 2.1: Section 10.6.4 "Absolutely positioned, non-replaced elements" |
| // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-non-replaced-height> |
| // (block-style-comments in this function and in |
| // computePositionedLogicalHeightUsing() |
| // correspond to text from the spec) |
| |
| // We don't use containingBlock(), since we may be positioned by an enclosing |
| // relpositioned inline. |
| const LayoutBoxModelObject* container_block = |
| ToLayoutBoxModelObject(Container()); |
| |
| const LayoutUnit container_logical_height = |
| ContainingBlockLogicalHeightForPositioned(container_block); |
| |
| const ComputedStyle& style_to_use = StyleRef(); |
| const LayoutUnit borders_plus_padding = BorderAndPaddingLogicalHeight(); |
| const Length margin_before = style_to_use.MarginBefore(); |
| const Length margin_after = style_to_use.MarginAfter(); |
| Length logical_top_length = style_to_use.LogicalTop(); |
| Length logical_bottom_length = style_to_use.LogicalBottom(); |
| |
| // --------------------------------------------------------------------------- |
| // For the purposes of this section and the next, the term "static position" |
| // (of an element) refers, roughly, to the position an element would have had |
| // in the normal flow. More precisely, the static position for 'top' is the |
| // distance from the top edge of the containing block to the top margin edge |
| // of a hypothetical box that would have been the first box of the element if |
| // its 'position' property had been 'static' and 'float' had been 'none'. The |
| // value is negative if the hypothetical box is above the containing block. |
| // |
| // But rather than actually calculating the dimensions of that hypothetical |
| // box, user agents are free to make a guess at its probable position. |
| // |
| // For the purposes of calculating the static position, the containing block |
| // of fixed positioned elements is the initial containing block instead of |
| // the viewport. |
| // --------------------------------------------------------------------------- |
| // see FIXME 1 |
| // Calculate the static distance if needed. |
| ComputeBlockStaticDistance(logical_top_length, logical_bottom_length, this, |
| container_block); |
| |
| // Calculate constraint equation values for 'height' case. |
| LayoutUnit logical_height = computed_values.extent_; |
| ComputePositionedLogicalHeightUsing( |
| kMainOrPreferredSize, style_to_use.LogicalHeight(), container_block, |
| container_logical_height, borders_plus_padding, logical_height, |
| logical_top_length, logical_bottom_length, margin_before, margin_after, |
| computed_values); |
| |
| // Avoid doing any work in the common case (where the values of min-height and |
| // max-height are their defaults). |
| // see FIXME 2 |
| |
| // Calculate constraint equation values for 'max-height' case. |
| if (!style_to_use.LogicalMaxHeight().IsMaxSizeNone()) { |
| LogicalExtentComputedValues max_values; |
| |
| ComputePositionedLogicalHeightUsing( |
| kMaxSize, style_to_use.LogicalMaxHeight(), container_block, |
| container_logical_height, borders_plus_padding, logical_height, |
| logical_top_length, logical_bottom_length, margin_before, margin_after, |
| max_values); |
| |
| if (computed_values.extent_ > max_values.extent_) { |
| computed_values.extent_ = max_values.extent_; |
| computed_values.position_ = max_values.position_; |
| computed_values.margins_.before_ = max_values.margins_.before_; |
| computed_values.margins_.after_ = max_values.margins_.after_; |
| } |
| } |
| |
| // Calculate constraint equation values for 'min-height' case. |
| if (!style_to_use.LogicalMinHeight().IsZero() || |
| style_to_use.LogicalMinHeight().IsIntrinsic()) { |
| LogicalExtentComputedValues min_values; |
| |
| ComputePositionedLogicalHeightUsing( |
| kMinSize, style_to_use.LogicalMinHeight(), container_block, |
| container_logical_height, borders_plus_padding, logical_height, |
| logical_top_length, logical_bottom_length, margin_before, margin_after, |
| min_values); |
| |
| if (computed_values.extent_ < min_values.extent_) { |
| computed_values.extent_ = min_values.extent_; |
| computed_values.position_ = min_values.position_; |
| computed_values.margins_.before_ = min_values.margins_.before_; |
| computed_values.margins_.after_ = min_values.margins_.after_; |
| } |
| } |
| |
| // Set final height value. |
| computed_values.extent_ += borders_plus_padding; |
| } |
| |
| void LayoutBox::ComputeLogicalTopPositionedOffset( |
| LayoutUnit& logical_top_pos, |
| const LayoutBox* child, |
| LayoutUnit logical_height_value, |
| const LayoutBoxModelObject* container_block, |
| LayoutUnit container_logical_height) { |
| // Deal with differing writing modes here. Our offset needs to be in the |
| // containing block's coordinate space. If the containing block is flipped |
| // along this axis, then we need to flip the coordinate. This can only happen |
| // if the containing block is both a flipped mode and perpendicular to us. |
| if ((child->Style()->IsFlippedBlocksWritingMode() && |
| child->IsHorizontalWritingMode() != |
| container_block->IsHorizontalWritingMode()) || |
| (child->Style()->IsFlippedBlocksWritingMode() != |
| container_block->Style()->IsFlippedBlocksWritingMode() && |
| child->IsHorizontalWritingMode() == |
| container_block->IsHorizontalWritingMode())) |
| logical_top_pos = |
| container_logical_height - logical_height_value - logical_top_pos; |
| |
| // Our offset is from the logical bottom edge in a flipped environment, e.g., |
| // right for vertical-rl. |
| if (container_block->Style()->IsFlippedBlocksWritingMode() && |
| child->IsHorizontalWritingMode() == |
| container_block->IsHorizontalWritingMode()) { |
| if (child->IsHorizontalWritingMode()) |
| logical_top_pos += container_block->BorderBottom(); |
| else |
| logical_top_pos += container_block->BorderRight(); |
| } else { |
| if (child->IsHorizontalWritingMode()) |
| logical_top_pos += container_block->BorderTop(); |
| else |
| logical_top_pos += container_block->BorderLeft(); |
| } |
| } |
| |
| void LayoutBox::ComputePositionedLogicalHeightUsing( |
| SizeType height_size_type, |
| Length logical_height_length, |
| const LayoutBoxModelObject* container_block, |
| LayoutUnit container_logical_height, |
| LayoutUnit borders_plus_padding, |
| LayoutUnit logical_height, |
| const Length& logical_top, |
| const Length& logical_bottom, |
| const Length& margin_before, |
| const Length& margin_after, |
| LogicalExtentComputedValues& computed_values) const { |
| DCHECK(height_size_type == kMinSize || |
| height_size_type == kMainOrPreferredSize || |
| !logical_height_length.IsAuto()); |
| if (height_size_type == kMinSize && logical_height_length.IsAuto()) |
| logical_height_length = Length(0, kFixed); |
| |
| // 'top' and 'bottom' cannot both be 'auto' because 'top would of been |
| // converted to the static position in computePositionedLogicalHeight() |
| DCHECK(!(logical_top.IsAuto() && logical_bottom.IsAuto())); |
| |
| LayoutUnit logical_height_value; |
| LayoutUnit content_logical_height = logical_height - borders_plus_padding; |
| |
| const LayoutUnit container_relative_logical_width = |
| ContainingBlockLogicalWidthForPositioned(container_block, false); |
| |
| LayoutUnit logical_top_value; |
| |
| bool logical_height_is_auto = logical_height_length.IsAuto(); |
| bool logical_top_is_auto = logical_top.IsAuto(); |
| bool logical_bottom_is_auto = logical_bottom.IsAuto(); |
| |
| LayoutUnit resolved_logical_height; |
| // Height is never unsolved for tables. |
| if (IsTable()) { |
| resolved_logical_height = content_logical_height; |
| logical_height_is_auto = false; |
| } else { |
| if (logical_height_length.IsIntrinsic()) |
| resolved_logical_height = ComputeIntrinsicLogicalContentHeightUsing( |
| logical_height_length, content_logical_height, borders_plus_padding); |
| else |
| resolved_logical_height = AdjustContentBoxLogicalHeightForBoxSizing( |
| ValueForLength(logical_height_length, container_logical_height)); |
| } |
| |
| if (!logical_top_is_auto && !logical_height_is_auto && |
| !logical_bottom_is_auto) { |
| // ------------------------------------------------------------------------- |
| // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' |
| // are 'auto', solve the equation under the extra constraint that the two |
| // margins get equal values. If one of 'margin-top' or 'margin- bottom' is |
| // 'auto', solve the equation for that value. If the values are over- |
| // constrained, ignore the value for 'bottom' and solve for that value. |
| // ------------------------------------------------------------------------- |
| // NOTE: It is not necessary to solve for 'bottom' in the over constrained |
| // case because the value is not used for any further calculations. |
| |
| logical_height_value = resolved_logical_height; |
| logical_top_value = ValueForLength(logical_top, container_logical_height); |
| |
| const LayoutUnit available_space = |
| container_logical_height - |
| (logical_top_value + logical_height_value + |
| ValueForLength(logical_bottom, container_logical_height) + |
| borders_plus_padding); |
| |
| // Margins are now the only unknown |
| if (margin_before.IsAuto() && margin_after.IsAuto()) { |
| // Both margins auto, solve for equality |
| // NOTE: This may result in negative values. |
| computed_values.margins_.before_ = |
| available_space / 2; // split the difference |
| computed_values.margins_.after_ = |
| available_space - computed_values.margins_ |
| .before_; // account for odd valued differences |
| } else if (margin_before.IsAuto()) { |
| // Solve for top margin |
| computed_values.margins_.after_ = |
| ValueForLength(margin_after, container_relative_logical_width); |
| computed_values.margins_.before_ = |
| available_space - computed_values.margins_.after_; |
| } else if (margin_after.IsAuto()) { |
| // Solve for bottom margin |
| computed_values.margins_.before_ = |
| ValueForLength(margin_before, container_relative_logical_width); |
| computed_values.margins_.after_ = |
| available_space - computed_values.margins_.before_; |
| } else { |
| // Over-constrained, (no need solve for bottom) |
| computed_values.margins_.before_ = |
| ValueForLength(margin_before, container_relative_logical_width); |
| computed_values.margins_.after_ = |
| ValueForLength(margin_after, container_relative_logical_width); |
| } |
| } else { |
| // ------------------------------------------------------------------------- |
| // Otherwise, set 'auto' values for 'margin-top' and 'margin-bottom' |
| // to 0, and pick the one of the following six rules that applies. |
| // |
| // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then |
| // the height is based on the content, and solve for 'top'. |
| // |
| // OMIT RULE 2 AS IT SHOULD NEVER BE HIT |
| // ------------------------------------------------------------------ |
| // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then |
| // set 'top' to the static position, and solve for 'bottom'. |
| // ------------------------------------------------------------------ |
| // |
| // 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then |
| // the height is based on the content, and solve for 'bottom'. |
| // 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', and |
| // solve for 'top'. |
| // 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', and |
| // solve for 'height'. |
| // 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', and |
| // solve for 'bottom'. |
| // ------------------------------------------------------------------------- |
| // NOTE: For rules 3 and 6 it is not necessary to solve for 'bottom' |
| // because the value is not used for any further calculations. |
| |
| // Calculate margins, 'auto' margins are ignored. |
| computed_values.margins_.before_ = |
| MinimumValueForLength(margin_before, container_relative_logical_width); |
| computed_values.margins_.after_ = |
| MinimumValueForLength(margin_after, container_relative_logical_width); |
| |
| const LayoutUnit available_space = |
| container_logical_height - |
| (computed_values.margins_.before_ + computed_values.margins_.after_ + |
| borders_plus_padding); |
| |
| // Use rule/case that applies. |
| if (logical_top_is_auto && logical_height_is_auto && |
| !logical_bottom_is_auto) { |
| // RULE 1: (height is content based, solve of top) |
| logical_height_value = content_logical_height; |
| logical_top_value = |
| available_space - |
| (logical_height_value + |
| ValueForLength(logical_bottom, container_logical_height)); |
| } else if (!logical_top_is_auto && logical_height_is_auto && |
| logical_bottom_is_auto) { |
| // RULE 3: (height is content based, no need solve of bottom) |
| logical_top_value = ValueForLength(logical_top, container_logical_height); |
| logical_height_value = content_logical_height; |
| } else if (logical_top_is_auto && !logical_height_is_auto && |
| !logical_bottom_is_auto) { |
| // RULE 4: (solve of top) |
| logical_height_value = resolved_logical_height; |
| logical_top_value = |
| available_space - |
| (logical_height_value + |
| ValueForLength(logical_bottom, container_logical_height)); |
| } else if (!logical_top_is_auto && logical_height_is_auto && |
| !logical_bottom_is_auto) { |
| // RULE 5: (solve of height) |
| logical_top_value = ValueForLength(logical_top, container_logical_height); |
| logical_height_value = std::max( |
| LayoutUnit(), |
| available_space - |
| (logical_top_value + |
| ValueForLength(logical_bottom, container_logical_height))); |
| } else if (!logical_top_is_auto && !logical_height_is_auto && |
| logical_bottom_is_auto) { |
| // RULE 6: (no need solve of bottom) |
| logical_height_value = resolved_logical_height; |
| logical_top_value = ValueForLength(logical_top, container_logical_height); |
| } |
| } |
| computed_values.extent_ = logical_height_value; |
| |
| // Use computed values to calculate the vertical position. |
| computed_values.position_ = |
| logical_top_value + computed_values.margins_.before_; |
| ComputeLogicalTopPositionedOffset(computed_values.position_, this, |
| logical_height_value, container_block, |
| container_logical_height); |
| } |
| |
| LayoutRect LayoutBox::LocalCaretRect(InlineBox* box, |
| int caret_offset, |
| LayoutUnit* extra_width_to_end_of_line) { |
| // VisiblePositions at offsets inside containers either a) refer to the |
| // positions before/after those containers (tables and select elements) or |
| // b) refer to the position inside an empty block. |
| // They never refer to children. |
| // FIXME: Paint the carets inside empty blocks differently than the carets |
| // before/after elements. |
| LayoutUnit caret_width = GetFrameView()->CaretWidth(); |
| LayoutRect rect(Location(), LayoutSize(caret_width, Size().Height())); |
| bool ltr = |
| box ? box->IsLeftToRightDirection() : Style()->IsLeftToRightDirection(); |
| |
| if ((!caret_offset) ^ ltr) |
| rect.Move(LayoutSize(Size().Width() - caret_width, LayoutUnit())); |
| |
| if (box) { |
| RootInlineBox& root_box = box->Root(); |
| LayoutUnit top = root_box.LineTop(); |
| rect.SetY(top); |
| rect.SetHeight(root_box.LineBottom() - top); |
| } |
| |
| // If height of box is smaller than font height, use the latter one, |
| // otherwise the caret might become invisible. |
| // |
| // Also, if the box is not an atomic inline-level element, always use the font |
| // height. This prevents the "big caret" bug described in: |
| // <rdar://problem/3777804> Deleting all content in a document can result in |
| // giant tall-as-window insertion point |
| // |
| // FIXME: ignoring :first-line, missing good reason to take care of |
| const SimpleFontData* font_data = Style()->GetFont().PrimaryFont(); |
| LayoutUnit font_height = |
| LayoutUnit(font_data ? font_data->GetFontMetrics().Height() : 0); |
| if (font_height > rect.Height() || (!IsAtomicInlineLevel() && !IsTable())) |
| rect.SetHeight(font_height); |
| |
| if (extra_width_to_end_of_line) |
| *extra_width_to_end_of_line = Location().X() + Size().Width() - rect.MaxX(); |
| |
| // Move to local coords |
| rect.MoveBy(-Location()); |
| |
| // FIXME: Border/padding should be added for all elements but this workaround |
| // is needed because we use offsets inside an "atomic" element to represent |
| // positions before and after the element in deprecated editing offsets. |
| if (GetNode() && |
| !(EditingIgnoresContent(*GetNode()) || IsDisplayInsideTable(GetNode()))) { |
| rect.SetX(rect.X() + BorderLeft() + PaddingLeft()); |
| rect.SetY(rect.Y() + PaddingTop() + BorderTop()); |
| } |
| |
| if (!IsHorizontalWritingMode()) |
| return rect.TransposedRect(); |
| |
| return rect; |
| } |
| |
| PositionWithAffinity LayoutBox::PositionForPoint(const LayoutPoint& point) { |
| // no children...return this layout object's element, if there is one, and |
| // offset 0 |
| LayoutObject* first_child = SlowFirstChild(); |
| if (!first_child) |
| return CreatePositionWithAffinity( |
| NonPseudoNode() ? FirstPositionInOrBeforeNode(NonPseudoNode()) |
| : Position()); |
| |
| if (IsTable() && NonPseudoNode()) { |
| LayoutUnit right = Size().Width() - VerticalScrollbarWidth(); |
| LayoutUnit bottom = Size().Height() - HorizontalScrollbarHeight(); |
| |
| if (point.X() < 0 || point.X() > right || point.Y() < 0 || |
| point.Y() > bottom) { |
| if (point.X() <= right / 2) |
| return CreatePositionWithAffinity( |
| FirstPositionInOrBeforeNode(NonPseudoNode())); |
| return CreatePositionWithAffinity( |
| LastPositionInOrAfterNode(NonPseudoNode())); |
| } |
| } |
| |
| // Pass off to the closest child. |
| LayoutUnit min_dist = LayoutUnit::Max(); |
| LayoutBox* closest_layout_object = nullptr; |
| LayoutPoint adjusted_point = point; |
| if (IsTableRow()) |
| adjusted_point.MoveBy(Location()); |
| |
| for (LayoutObject* layout_object = first_child; layout_object; |
| layout_object = layout_object->NextSibling()) { |
| if ((!layout_object->SlowFirstChild() && !layout_object->IsInline() && |
| !layout_object->IsLayoutBlockFlow()) || |
| layout_object->Style()->Visibility() != EVisibility::kVisible) |
| continue; |
| |
| if (!layout_object->IsBox()) |
| continue; |
| |
| LayoutBox* layout_box = ToLayoutBox(layout_object); |
| |
| LayoutUnit top = layout_box->BorderTop() + layout_box->PaddingTop() + |
| (IsTableRow() ? LayoutUnit() : layout_box->Location().Y()); |
| LayoutUnit bottom = top + layout_box->ContentHeight(); |
| LayoutUnit left = |
| layout_box->BorderLeft() + layout_box->PaddingLeft() + |
| (IsTableRow() ? LayoutUnit() : layout_box->Location().X()); |
| LayoutUnit right = left + layout_box->ContentWidth(); |
| |
| if (point.X() <= right && point.X() >= left && point.Y() <= top && |
| point.Y() >= bottom) { |
| if (layout_box->IsTableRow()) |
| return layout_box->PositionForPoint(point + adjusted_point - |
| layout_box->LocationOffset()); |
| return layout_box->PositionForPoint(point - layout_box->LocationOffset()); |
| } |
| |
| // Find the distance from (x, y) to the box. Split the space around the box |
| // into 8 pieces and use a different compare depending on which piece (x, y) |
| // is in. |
| LayoutPoint cmp; |
| if (point.X() > right) { |
| if (point.Y() < top) |
| cmp = LayoutPoint(right, top); |
| else if (point.Y() > bottom) |
| cmp = LayoutPoint(right, bottom); |
| else |
| cmp = LayoutPoint(right, point.Y()); |
| } else if (point.X() < left) { |
| if (point.Y() < top) |
| cmp = LayoutPoint(left, top); |
| else if (point.Y() > bottom) |
| cmp = LayoutPoint(left, bottom); |
| else |
| cmp = LayoutPoint(left, point.Y()); |
| } else { |
| if (point.Y() < top) |
| cmp = LayoutPoint(point.X(), top); |
| else |
| cmp = LayoutPoint(point.X(), bottom); |
| } |
| |
| LayoutSize difference = cmp - point; |
| |
| LayoutUnit dist = difference.Width() * difference.Width() + |
| difference.Height() * difference.Height(); |
| if (dist < min_dist) { |
| closest_layout_object = layout_box; |
| min_dist = dist; |
| } |
| } |
| |
| if (closest_layout_object) |
| return closest_layout_object->PositionForPoint( |
| adjusted_point - closest_layout_object->LocationOffset()); |
| return CreatePositionWithAffinity( |
| FirstPositionInOrBeforeNode(NonPseudoNode())); |
| } |
| |
| DISABLE_CFI_PERF |
| bool LayoutBox::ShrinkToAvoidFloats() const { |
| // Floating objects don't shrink. Objects that don't avoid floats don't |
| // shrink. |
| if (IsInline() || !AvoidsFloats() || IsFloating()) |
| return false; |
| |
| // Only auto width objects can possibly shrink to avoid floats. |
| return Style()->Width().IsAuto(); |
| } |
| |
| DISABLE_CFI_PERF |
| bool LayoutBox::ShouldBeConsideredAsReplaced() const { |
| // Checkboxes and radioboxes are not isAtomicInlineLevel() nor do they have |
| // their own layoutObject in which to override avoidFloats(). |
| if (IsAtomicInlineLevel()) |
| return true; |
| Node* node = this->GetNode(); |
| return node && node->IsElementNode() && |
| (ToElement(node)->IsFormControlElement() || |
| isHTMLImageElement(ToElement(node))); |
| } |
| |
| bool LayoutBox::AvoidsFloats() const { |
| return true; |
| } |
| |
| bool LayoutBox::HasNonCompositedScrollbars() const { |
| if (PaintLayerScrollableArea* scrollable_area = this->GetScrollableArea()) { |
| if (scrollable_area->HasHorizontalScrollbar() && |
| !scrollable_area->LayerForHorizontalScrollbar()) |
| return true; |
| if (scrollable_area->HasVerticalScrollbar() && |
| !scrollable_area->LayerForVerticalScrollbar()) |
| return true; |
| } |
| return false; |
| } |
| |
| void LayoutBox::UpdateFragmentationInfoForChild(LayoutBox& child) { |
| LayoutState* layout_state = View()->GetLayoutState(); |
| DCHECK(layout_state->IsPaginated()); |
| child.SetOffsetToNextPage(LayoutUnit()); |
| if (!IsPageLogicalHeightKnown()) |
| return; |
| |
| LayoutUnit logical_top = child.LogicalTop(); |
| LayoutUnit logical_height = child.LogicalHeightWithVisibleOverflow(); |
| LayoutUnit space_left = PageRemainingLogicalHeightForOffset( |
| logical_top, kAssociateWithLatterPage); |
| if (space_left < logical_height) |
| child.SetOffsetToNextPage(space_left); |
| } |
| |
| bool LayoutBox::ChildNeedsRelayoutForPagination(const LayoutBox& child) const { |
| // TODO(mstensho): Should try to get this to work for floats too, instead of |
| // just marking and bailing here. |
| if (child.IsFloating()) |
| return true; |
| const LayoutFlowThread* flow_thread = child.FlowThreadContainingBlock(); |
| // Figure out if we really need to force re-layout of the child. We only need |
| // to do this if there's a chance that we need to recalculate pagination |
| // struts inside. |
| if (IsPageLogicalHeightKnown()) { |
| LayoutUnit logical_top = child.LogicalTop(); |
| LayoutUnit logical_height = child.LogicalHeightWithVisibleOverflow(); |
| LayoutUnit remaining_space = PageRemainingLogicalHeightForOffset( |
| logical_top, kAssociateWithLatterPage); |
| if (child.OffsetToNextPage()) { |
| // We need to relayout unless we're going to break at the exact same |
| // location as before. |
| if (child.OffsetToNextPage() != remaining_space) |
| return true; |
| // If column height isn't guaranteed to be uniform, we have no way of |
| // telling what has happened after the first break. |
| if (flow_thread && flow_thread->MayHaveNonUniformPageLogicalHeight()) |
| return true; |
| } else if (logical_height > remaining_space) { |
| // Last time we laid out this child, we didn't need to break, but now we |
| // have to. So we need to relayout. |
| return true; |
| } |
| } else if (child.OffsetToNextPage()) { |
| // This child did previously break, but it won't anymore, because we no |
| // longer have a known fragmentainer height. |
| return true; |
| } |
| |
| // It seems that we can skip layout of this child, but we need to ask the flow |
| // thread for permission first. We currently cannot skip over objects |
| // containing column spanners. |
| return flow_thread && !flow_thread->CanSkipLayout(child); |
| } |
| |
| void LayoutBox::MarkChildForPaginationRelayoutIfNeeded( |
| LayoutBox& child, |
| SubtreeLayoutScope& layout_scope) { |
| DCHECK(!child.NeedsLayout()); |
| LayoutState* layout_state = View()->GetLayoutState(); |
| |
| if (layout_state->PaginationStateChanged() || |
| (layout_state->IsPaginated() && ChildNeedsRelayoutForPagination(child))) |
| layout_scope.SetChildNeedsLayout(&child); |
| } |
| |
| void LayoutBox::MarkOrthogonalWritingModeRoot() { |
| DCHECK(GetFrameView()); |
| GetFrameView()->AddOrthogonalWritingModeRoot(*this); |
| } |
| |
| void LayoutBox::UnmarkOrthogonalWritingModeRoot() { |
| DCHECK(GetFrameView()); |
| GetFrameView()->RemoveOrthogonalWritingModeRoot(*this); |
| } |
| |
| bool LayoutBox::IsRenderedLegend() const { |
| if (!isHTMLLegendElement(GetNode())) |
| return false; |
| if (IsFloatingOrOutOfFlowPositioned()) |
| return false; |
| const auto* parent = Parent(); |
| return parent && parent->IsFieldset() && |
| ToLayoutFieldset(parent)->FindInFlowLegend() == this; |
| } |
| |
| void LayoutBox::AddVisualEffectOverflow() { |
| if (!Style()->HasVisualOverflowingEffect()) |
| return; |
| |
| // Add in the final overflow with shadows, outsets and outline combined. |
| LayoutRect visual_effect_overflow = BorderBoxRect(); |
| visual_effect_overflow.Expand(ComputeVisualEffectOverflowOutsets()); |
| AddSelfVisualOverflow(visual_effect_overflow); |
| } |
| |
| LayoutRectOutsets LayoutBox::ComputeVisualEffectOverflowOutsets() { |
| DCHECK(Style()->HasVisualOverflowingEffect()); |
| |
| LayoutUnit top; |
| LayoutUnit right; |
| LayoutUnit bottom; |
| LayoutUnit left; |
| |
| if (const ShadowList* box_shadow = Style()->BoxShadow()) { |
| // FIXME: Use LayoutUnit edge outsets, and then simplify this. |
| FloatRectOutsets outsets = box_shadow->RectOutsetsIncludingOriginal(); |
| top = LayoutUnit(outsets.Top()); |
| right = LayoutUnit(outsets.Right()); |
| bottom = LayoutUnit(outsets.Bottom()); |
| left = LayoutUnit(outsets.Left()); |
| } |
| |
| if (Style()->HasBorderImageOutsets()) { |
| LayoutRectOutsets border_outsets = Style()->BorderImageOutsets(); |
| top = std::max(top, border_outsets.Top()); |
| right = std::max(right, border_outsets.Right()); |
| bottom = std::max(bottom, border_outsets.Bottom()); |
| left = std::max(left, border_outsets.Left()); |
| } |
| |
| // Box-shadow and border-image-outsets are in physical direction. Flip into |
| // block direction. |
| if (UNLIKELY(HasFlippedBlocksWritingMode())) |
| std::swap(left, right); |
| |
| if (Style()->HasOutline()) { |
| Vector<LayoutRect> outline_rects; |
| // The result rects are in coordinates of this object's border box. |
| AddOutlineRects(outline_rects, LayoutPoint(), |
| OutlineRectsShouldIncludeBlockVisualOverflow()); |
| LayoutRect rect = UnionRectEvenIfEmpty(outline_rects); |
| SetOutlineMayBeAffectedByDescendants(rect.Size() != Size()); |
| |
| int outline_outset = Style()->OutlineOutsetExtent(); |
| top = std::max(top, -rect.Y() + outline_outset); |
| right = std::max(right, rect.MaxX() - Size().Width() + outline_outset); |
| bottom = std::max(bottom, rect.MaxY() - Size().Height() + outline_outset); |
| left = std::max(left, -rect.X() + outline_outset); |
| } |
| |
| return LayoutRectOutsets(top, right, bottom, left); |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBox::AddOverflowFromChild(const LayoutBox& child, |
| const LayoutSize& delta) { |
| // Never allow flow threads to propagate overflow up to a parent. |
| if (child.IsLayoutFlowThread()) |
| return; |
| |
| // Only propagate layout overflow from the child if the child isn't clipping |
| // its overflow. If it is, then its overflow is internal to it, and we don't |
| // care about it. LayoutOverflowRectForPropagation takes care of this and just |
| // propagates the border box rect instead. |
| LayoutRect child_layout_overflow_rect = |
| child.LayoutOverflowRectForPropagation(this); |
| child_layout_overflow_rect.Move(delta); |
| AddLayoutOverflow(child_layout_overflow_rect); |
| |
| // Add in visual overflow from the child. Even if the child clips its |
| // overflow, it may still have visual overflow of its own set from box shadows |
| // or reflections. It is unnecessary to propagate this overflow if we are |
| // clipping our own overflow. |
| if (child.HasSelfPaintingLayer()) |
| return; |
| LayoutRect child_visual_overflow_rect = |
| child.VisualOverflowRectForPropagation(); |
| child_visual_overflow_rect.Move(delta); |
| AddContentsVisualOverflow(child_visual_overflow_rect); |
| } |
| |
| bool LayoutBox::HasTopOverflow() const { |
| return !Style()->IsLeftToRightDirection() && !IsHorizontalWritingMode(); |
| } |
| |
| bool LayoutBox::HasLeftOverflow() const { |
| return !Style()->IsLeftToRightDirection() && IsHorizontalWritingMode(); |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBox::AddLayoutOverflow(const LayoutRect& rect) { |
| if (rect.IsEmpty()) |
| return; |
| |
| LayoutRect client_box = NoOverflowRect(); |
| if (client_box.Contains(rect)) |
| return; |
| |
| // For overflow clip objects, we don't want to propagate overflow into |
| // unreachable areas. |
| LayoutRect overflow_rect(rect); |
| if (HasOverflowClip() || IsLayoutView()) { |
| // Overflow is in the block's coordinate space and thus is flipped for |
| // vertical-rl writing |
| // mode. At this stage that is actually a simplification, since we can |
| // treat vertical-lr/rl |
| // as the same. |
| if (HasTopOverflow()) |
| overflow_rect.ShiftMaxYEdgeTo( |
| std::min(overflow_rect.MaxY(), client_box.MaxY())); |
| else |
| overflow_rect.ShiftYEdgeTo(std::max(overflow_rect.Y(), client_box.Y())); |
| if (HasLeftOverflow()) |
| overflow_rect.ShiftMaxXEdgeTo( |
| std::min(overflow_rect.MaxX(), client_box.MaxX())); |
| else |
| overflow_rect.ShiftXEdgeTo(std::max(overflow_rect.X(), client_box.X())); |
| |
| // Now re-test with the adjusted rectangle and see if it has become |
| // unreachable or fully |
| // contained. |
| if (client_box.Contains(overflow_rect) || overflow_rect.IsEmpty()) |
| return; |
| } |
| |
| if (!overflow_) { |
| overflow_ = |
| WTF::WrapUnique(new BoxOverflowModel(client_box, BorderBoxRect())); |
| } |
| |
| overflow_->AddLayoutOverflow(overflow_rect); |
| } |
| |
| void LayoutBox::AddSelfVisualOverflow(const LayoutRect& rect) { |
| if (rect.IsEmpty()) |
| return; |
| |
| LayoutRect border_box = BorderBoxRect(); |
| if (border_box.Contains(rect)) |
| return; |
| |
| if (!overflow_) { |
| overflow_ = |
| WTF::WrapUnique(new BoxOverflowModel(NoOverflowRect(), border_box)); |
| } |
| |
| overflow_->AddSelfVisualOverflow(rect); |
| } |
| |
| void LayoutBox::AddContentsVisualOverflow(const LayoutRect& rect) { |
| if (rect.IsEmpty()) |
| return; |
| |
| // If hasOverflowClip() we always save contents visual overflow because we |
| // need it |
| // e.g. to determine whether to apply rounded corner clip on contents. |
| // Otherwise we save contents visual overflow only if it overflows the border |
| // box. |
| LayoutRect border_box = BorderBoxRect(); |
| if (!HasOverflowClip() && border_box.Contains(rect)) |
| return; |
| |
| if (!overflow_) { |
| overflow_ = |
| WTF::WrapUnique(new BoxOverflowModel(NoOverflowRect(), border_box)); |
| } |
| overflow_->AddContentsVisualOverflow(rect); |
| } |
| |
| void LayoutBox::ClearLayoutOverflow() { |
| if (!overflow_) |
| return; |
| |
| if (!HasSelfVisualOverflow() && ContentsVisualOverflowRect().IsEmpty()) { |
| ClearAllOverflows(); |
| return; |
| } |
| |
| overflow_->SetLayoutOverflow(NoOverflowRect()); |
| } |
| |
| bool LayoutBox::PercentageLogicalHeightIsResolvable() const { |
| Length fake_length(100, kPercent); |
| return ComputePercentageLogicalHeight(fake_length) != -1; |
| } |
| |
| DISABLE_CFI_PERF |
| bool LayoutBox::HasUnsplittableScrollingOverflow() const { |
| // We will paginate as long as we don't scroll overflow in the pagination |
| // direction. |
| bool is_horizontal = IsHorizontalWritingMode(); |
| if ((is_horizontal && !ScrollsOverflowY()) || |
| (!is_horizontal && !ScrollsOverflowX())) |
| return false; |
| |
| // Fragmenting scrollbars is only problematic in interactive media, e.g. |
| // multicol on a screen. If we're printing, which is non-interactive media, we |
| // should allow objects with non-visible overflow to be paginated as normally. |
| if (GetDocument().Printing()) |
| return false; |
| |
| // We do have overflow. We'll still be willing to paginate as long as the |
| // block has auto logical height, auto or undefined max-logical-height and a |
| // zero or auto min-logical-height. |
| // Note this is just a heuristic, and it's still possible to have overflow |
| // under these conditions, but it should work out to be good enough for common |
| // cases. Paginating overflow with scrollbars present is not the end of the |
| // world and is what we used to do in the old model anyway. |
| return !Style()->LogicalHeight().IsIntrinsicOrAuto() || |
| (!Style()->LogicalMaxHeight().IsIntrinsicOrAuto() && |
| !Style()->LogicalMaxHeight().IsMaxSizeNone() && |
| (!Style()->LogicalMaxHeight().IsPercentOrCalc() || |
| PercentageLogicalHeightIsResolvable())) || |
| (!Style()->LogicalMinHeight().IsIntrinsicOrAuto() && |
| Style()->LogicalMinHeight().IsPositive() && |
| (!Style()->LogicalMinHeight().IsPercentOrCalc() || |
| PercentageLogicalHeightIsResolvable())); |
| } |
| |
| LayoutBox::PaginationBreakability LayoutBox::GetPaginationBreakability() const { |
| // TODO(mstensho): It is wrong to check isAtomicInlineLevel() as we |
| // actually look for replaced elements. |
| if (IsAtomicInlineLevel() || HasUnsplittableScrollingOverflow() || |
| (Parent() && IsWritingModeRoot()) || |
| (IsOutOfFlowPositioned() && Style()->GetPosition() == EPosition::kFixed)) |
| return kForbidBreaks; |
| |
| EBreakInside break_value = BreakInside(); |
| if (break_value == EBreakInside::kAvoid || |
| break_value == EBreakInside::kAvoidPage || |
| break_value == EBreakInside::kAvoidColumn) |
| return kAvoidBreaks; |
| return kAllowAnyBreaks; |
| } |
| |
| LayoutUnit LayoutBox::LineHeight(bool /*firstLine*/, |
| LineDirectionMode direction, |
| LinePositionMode /*linePositionMode*/) const { |
| if (IsAtomicInlineLevel()) { |
| return direction == kHorizontalLine ? MarginHeight() + Size().Height() |
| : MarginWidth() + Size().Width(); |
| } |
| return LayoutUnit(); |
| } |
| |
| DISABLE_CFI_PERF |
| int LayoutBox::BaselinePosition(FontBaseline baseline_type, |
| bool /*firstLine*/, |
| LineDirectionMode direction, |
| LinePositionMode line_position_mode) const { |
| DCHECK_EQ(line_position_mode, kPositionOnContainingLine); |
| if (IsAtomicInlineLevel()) { |
| int result = direction == kHorizontalLine |
| ? RoundToInt(MarginHeight() + Size().Height()) |
| : RoundToInt(MarginWidth() + Size().Width()); |
| if (baseline_type == kAlphabeticBaseline) |
| return result; |
| return result - result / 2; |
| } |
| return 0; |
| } |
| |
| PaintLayer* LayoutBox::EnclosingFloatPaintingLayer() const { |
| const LayoutObject* curr = this; |
| while (curr) { |
| PaintLayer* layer = |
| curr->HasLayer() && curr->IsBox() ? ToLayoutBox(curr)->Layer() : 0; |
| if (layer && layer->IsSelfPaintingLayer()) |
| return layer; |
| curr = curr->Parent(); |
| } |
| return nullptr; |
| } |
| |
| LayoutRect LayoutBox::LogicalVisualOverflowRectForPropagation() const { |
| LayoutRect rect = VisualOverflowRectForPropagation(); |
| if (!Parent()->StyleRef().IsHorizontalWritingMode()) |
| return rect.TransposedRect(); |
| return rect; |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutRect LayoutBox::RectForOverflowPropagation(const LayoutRect& rect) const { |
| // If the child and parent are in the same blocks direction, then we don't |
| // have to do anything fancy. Just return the rect. |
| if (Parent()->StyleRef().IsFlippedBlocksWritingMode() == |
| StyleRef().IsFlippedBlocksWritingMode()) |
| return rect; |
| |
| // Convert the rect into parent's blocks direction by flipping along the y |
| // axis. |
| LayoutRect result = rect; |
| result.SetX(Size().Width() - rect.MaxX()); |
| return result; |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutRect LayoutBox::LogicalLayoutOverflowRectForPropagation( |
| LayoutObject* container) const { |
| LayoutRect rect = LayoutOverflowRectForPropagation(container); |
| if (!Parent()->StyleRef().IsHorizontalWritingMode()) |
| return rect.TransposedRect(); |
| return rect; |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutRect LayoutBox::LayoutOverflowRectForPropagation( |
| LayoutObject* container) const { |
| // Only propagate interior layout overflow if we don't clip it. |
| LayoutRect rect = BorderBoxRect(); |
| // We want to include the margin, but only when it adds height. Quirky margins |
| // don't contribute height nor do the margins of self-collapsing blocks. |
| if (!StyleRef().HasMarginAfterQuirk() && !IsSelfCollapsingBlock()) |
| rect.Expand(IsHorizontalWritingMode() |
| ? LayoutSize(LayoutUnit(), MarginAfter()) |
| : LayoutSize(MarginAfter(), LayoutUnit())); |
| |
| if (!HasOverflowClip()) |
| rect.Unite(LayoutOverflowRect()); |
| |
| bool has_transform = HasLayer() && Layer()->Transform(); |
| if (IsInFlowPositioned() || has_transform) { |
| // If we are relatively positioned or if we have a transform, then we have |
| // to convert this rectangle into physical coordinates, apply relative |
| // positioning and transforms to it, and then convert it back. |
| FlipForWritingMode(rect); |
| |
| LayoutSize container_offset; |
| |
| if (IsInFlowPositioned()) |
| container_offset = OffsetForInFlowPosition(); |
| |
| if (ShouldUseTransformFromContainer(container)) { |
| TransformationMatrix t; |
| GetTransformFromContainer(container ? container : Container(), |
| container_offset, t); |
| rect = t.MapRect(rect); |
| } else { |
| rect.Move(container_offset); |
| } |
| |
| // Now we need to flip back. |
| FlipForWritingMode(rect); |
| } |
| |
| return RectForOverflowPropagation(rect); |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutRect LayoutBox::NoOverflowRect() const { |
| // Because of the special coordinate system used for overflow rectangles and |
| // many other rectangles (not quite logical, not quite physical), we need to |
| // flip the block progression coordinate in vertical-rl writing mode. In other |
| // words, the rectangle returned is physical, except for the block direction |
| // progression coordinate (x in vertical writing mode), which is always |
| // "logical top". Apart from the flipping, this method does the same thing as |
| // clientBoxRect(). |
| |
| const int scroll_bar_width = VerticalScrollbarWidth(); |
| const int scroll_bar_height = HorizontalScrollbarHeight(); |
| LayoutUnit left(BorderLeft() + |
| (ShouldPlaceBlockDirectionScrollbarOnLogicalLeft() |
| ? scroll_bar_width |
| : 0)); |
| LayoutUnit top(BorderTop()); |
| LayoutUnit right(BorderRight()); |
| LayoutUnit bottom(BorderBottom()); |
| LayoutRect rect(left, top, Size().Width() - left - right, |
| Size().Height() - top - bottom); |
| FlipForWritingMode(rect); |
| // Subtract space occupied by scrollbars. Order is important here: first flip, |
| // then subtract scrollbars. This may seem backwards and weird, since one |
| // would think that a vertical scrollbar at the physical right in vertical-rl |
| // ought to be at the logical left (physical right), between the logical left |
| // (physical right) border and the logical left (physical right) padding. But |
| // this is how the rest of the code expects us to behave. This is highly |
| // related to https://bugs.webkit.org/show_bug.cgi?id=76129 |
| // FIXME: when the above mentioned bug is fixed, it should hopefully be |
| // possible to call clientBoxRect() or paddingBoxRect() in this method, rather |
| // than fiddling with the edges on our own. |
| if (ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) |
| rect.Contract(0, scroll_bar_height); |
| else |
| rect.Contract(scroll_bar_width, scroll_bar_height); |
| return rect; |
| } |
| |
| LayoutRect LayoutBox::VisualOverflowRect() const { |
| if (!overflow_) |
| return BorderBoxRect(); |
| if (HasOverflowClip()) |
| return overflow_->SelfVisualOverflowRect(); |
| return UnionRect(overflow_->SelfVisualOverflowRect(), |
| overflow_->ContentsVisualOverflowRect()); |
| } |
| |
| LayoutUnit LayoutBox::OffsetLeft(const Element* parent) const { |
| return AdjustedPositionRelativeTo(PhysicalLocation(), parent).X(); |
| } |
| |
| LayoutUnit LayoutBox::OffsetTop(const Element* parent) const { |
| return AdjustedPositionRelativeTo(PhysicalLocation(), parent).Y(); |
| } |
| |
| LayoutPoint LayoutBox::FlipForWritingModeForChild( |
| const LayoutBox* child, |
| const LayoutPoint& point) const { |
| if (!Style()->IsFlippedBlocksWritingMode()) |
| return point; |
| |
| // The child is going to add in its x(), so we have to make sure it ends up in |
| // the right place. |
| return LayoutPoint(point.X() + Size().Width() - child->Size().Width() - |
| (2 * child->Location().X()), |
| point.Y()); |
| } |
| |
| LayoutBox* LayoutBox::LocationContainer() const { |
| // Location of a non-root SVG object derived from LayoutBox should not be |
| // affected by writing-mode of the containing box (SVGRoot). |
| if (IsSVGChild()) |
| return nullptr; |
| |
| // Normally the box's location is relative to its containing box. |
| LayoutObject* container = this->Container(); |
| while (container && !container->IsBox()) |
| container = container->Container(); |
| return ToLayoutBox(container); |
| } |
| |
| LayoutPoint LayoutBox::PhysicalLocation( |
| const LayoutBox* flipped_blocks_container) const { |
| const LayoutBox* container_box; |
| if (flipped_blocks_container) { |
| DCHECK_EQ(flipped_blocks_container, LocationContainer()); |
| container_box = flipped_blocks_container; |
| } else { |
| container_box = LocationContainer(); |
| } |
| if (!container_box) |
| return Location(); |
| return container_box->FlipForWritingModeForChild(this, Location()); |
| } |
| |
| bool LayoutBox::HasRelativeLogicalWidth() const { |
| return Style()->LogicalWidth().IsPercentOrCalc() || |
| Style()->LogicalMinWidth().IsPercentOrCalc() || |
| Style()->LogicalMaxWidth().IsPercentOrCalc(); |
| } |
| |
| bool LayoutBox::HasRelativeLogicalHeight() const { |
| return Style()->LogicalHeight().IsPercentOrCalc() || |
| Style()->LogicalMinHeight().IsPercentOrCalc() || |
| Style()->LogicalMaxHeight().IsPercentOrCalc(); |
| } |
| |
| static void MarkBoxForRelayoutAfterSplit(LayoutBox* box) { |
| // FIXME: The table code should handle that automatically. If not, |
| // we should fix it and remove the table part checks. |
| if (box->IsTable()) { |
| // Because we may have added some sections with already computed column |
| // structures, we need to sync the table structure with them now. This |
| // avoids crashes when adding new cells to the table. |
| ToLayoutTable(box)->ForceSectionsRecalc(); |
| } else if (box->IsTableSection()) { |
| ToLayoutTableSection(box)->SetNeedsCellRecalc(); |
| } |
| |
| box->SetNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::kAnonymousBlockChange); |
| } |
| |
| static void CollapseLoneAnonymousBlockChild(LayoutBox* parent, |
| LayoutObject* child) { |
| if (!child->IsAnonymousBlock() || !child->IsLayoutBlockFlow()) |
| return; |
| if (!parent->IsLayoutBlockFlow()) |
| return; |
| ToLayoutBlockFlow(parent)->CollapseAnonymousBlockChild( |
| ToLayoutBlockFlow(child)); |
| } |
| |
| LayoutObject* LayoutBox::SplitAnonymousBoxesAroundChild( |
| LayoutObject* before_child) { |
| LayoutBox* box_at_top_of_new_branch = nullptr; |
| |
| while (before_child->Parent() != this) { |
| LayoutBox* box_to_split = ToLayoutBox(before_child->Parent()); |
| if (box_to_split->SlowFirstChild() != before_child && |
| box_to_split->IsAnonymous()) { |
| // We have to split the parent box into two boxes and move children |
| // from |beforeChild| to end into the new post box. |
| LayoutBox* post_box = |
| box_to_split->CreateAnonymousBoxWithSameTypeAs(this); |
| post_box->SetChildrenInline(box_to_split->ChildrenInline()); |
| LayoutBox* parent_box = ToLayoutBox(box_to_split->Parent()); |
| // We need to invalidate the |parentBox| before inserting the new node |
| // so that the table paint invalidation logic knows the structure is |
| // dirty. See for example LayoutTableCell:localVisualRect(). |
| MarkBoxForRelayoutAfterSplit(parent_box); |
| parent_box->VirtualChildren()->InsertChildNode( |
| parent_box, post_box, box_to_split->NextSibling()); |
| box_to_split->MoveChildrenTo(post_box, before_child, 0, true); |
| |
| LayoutObject* child = post_box->SlowFirstChild(); |
| DCHECK(child); |
| if (child && !child->NextSibling()) |
| CollapseLoneAnonymousBlockChild(post_box, child); |
| child = box_to_split->SlowFirstChild(); |
| DCHECK(child); |
| if (child && !child->NextSibling()) |
| CollapseLoneAnonymousBlockChild(box_to_split, child); |
| |
| MarkBoxForRelayoutAfterSplit(box_to_split); |
| MarkBoxForRelayoutAfterSplit(post_box); |
| box_at_top_of_new_branch = post_box; |
| |
| before_child = post_box; |
| } else { |
| before_child = box_to_split; |
| } |
| } |
| |
| // Splitting the box means the left side of the container chain will lose any |
| // percent height descendants below |boxAtTopOfNewBranch| on the right hand |
| // side. |
| if (box_at_top_of_new_branch) { |
| box_at_top_of_new_branch->ClearPercentHeightDescendants(); |
| MarkBoxForRelayoutAfterSplit(this); |
| } |
| |
| DCHECK_EQ(before_child->Parent(), this); |
| return before_child; |
| } |
| |
| LayoutUnit LayoutBox::OffsetFromLogicalTopOfFirstPage() const { |
| LayoutState* layout_state = View()->GetLayoutState(); |
| if (!layout_state || !layout_state->IsPaginated()) |
| return LayoutUnit(); |
| |
| if (layout_state->GetLayoutObject() == this) { |
| LayoutSize offset = layout_state->PaginationOffset(); |
| return IsHorizontalWritingMode() ? offset.Height() : offset.Width(); |
| } |
| |
| // A LayoutBlock always establishes a layout state, and this method is only |
| // meant to be called on the object currently being laid out. |
| DCHECK(!IsLayoutBlock()); |
| |
| // In case this box doesn't establish a layout state, try the containing |
| // block. |
| LayoutBlock* container_block = ContainingBlock(); |
| DCHECK(layout_state->GetLayoutObject() == container_block); |
| return container_block->OffsetFromLogicalTopOfFirstPage() + LogicalTop(); |
| } |
| |
| void LayoutBox::SetOffsetToNextPage(LayoutUnit offset) { |
| if (!rare_data_ && !offset) |
| return; |
| EnsureRareData().offset_to_next_page_ = offset; |
| } |
| |
| void LayoutBox::LogicalExtentAfterUpdatingLogicalWidth( |
| const LayoutUnit& new_logical_top, |
| LayoutBox::LogicalExtentComputedValues& computed_values) { |
| // FIXME: None of this is right for perpendicular writing-mode children. |
| LayoutUnit old_logical_width = LogicalWidth(); |
| LayoutUnit old_logical_left = LogicalLeft(); |
| LayoutUnit old_margin_left = MarginLeft(); |
| LayoutUnit old_margin_right = MarginRight(); |
| LayoutUnit old_logical_top = LogicalTop(); |
| |
| SetLogicalTop(new_logical_top); |
| UpdateLogicalWidth(); |
| |
| computed_values.extent_ = LogicalWidth(); |
| computed_values.position_ = LogicalLeft(); |
| computed_values.margins_.start_ = MarginStart(); |
| computed_values.margins_.end_ = MarginEnd(); |
| |
| SetLogicalTop(old_logical_top); |
| SetLogicalWidth(old_logical_width); |
| SetLogicalLeft(old_logical_left); |
| SetMarginLeft(old_margin_left); |
| SetMarginRight(old_margin_right); |
| } |
| |
| bool LayoutBox::MustInvalidateFillLayersPaintOnHeightChange( |
| const FillLayer& layer) { |
| // Nobody will use multiple layers without wanting fancy positioning. |
| if (layer.Next()) |
| return true; |
| |
| // Make sure we have a valid image. |
| StyleImage* img = layer.GetImage(); |
| if (!img || !img->CanRender()) |
| return false; |
| |
| if (layer.RepeatY() != kRepeatFill && layer.RepeatY() != kNoRepeatFill) |
| return true; |
| |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| if (layer.YPosition().IsPercentOrCalc() && !layer.YPosition().IsZero()) |
| return true; |
| |
| if (layer.BackgroundYOrigin() != kTopEdge) |
| return true; |
| |
| EFillSizeType size_type = layer.SizeType(); |
| |
| if (size_type == kContain || size_type == kCover) |
| return true; |
| |
| if (size_type == kSizeLength) { |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| if (layer.SizeLength().Height().IsPercentOrCalc() && |
| !layer.SizeLength().Height().IsZero()) |
| return true; |
| if (img->IsGeneratedImage() && layer.SizeLength().Height().IsAuto()) |
| return true; |
| } else if (img->UsesImageContainerSize()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool LayoutBox::MustInvalidateFillLayersPaintOnWidthChange( |
| const FillLayer& layer) { |
| // Nobody will use multiple layers without wanting fancy positioning. |
| if (layer.Next()) |
| return true; |
| |
| // Make sure we have a valid image. |
| StyleImage* img = layer.GetImage(); |
| if (!img || !img->CanRender()) |
| return false; |
| |
| if (layer.RepeatX() != kRepeatFill && layer.RepeatX() != kNoRepeatFill) |
| return true; |
| |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| if (layer.XPosition().IsPercentOrCalc() && !layer.XPosition().IsZero()) |
| return true; |
| |
| if (layer.BackgroundXOrigin() != kLeftEdge) |
| return true; |
| |
| EFillSizeType size_type = layer.SizeType(); |
| |
| if (size_type == kContain || size_type == kCover) |
| return true; |
| |
| if (size_type == kSizeLength) { |
| // TODO(alancutter): Make this work correctly for calc lengths. |
| if (layer.SizeLength().Width().IsPercentOrCalc() && |
| !layer.SizeLength().Width().IsZero()) |
| return true; |
| if (img->IsGeneratedImage() && layer.SizeLength().Width().IsAuto()) |
| return true; |
| } else if (img->UsesImageContainerSize()) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool LayoutBox::MustInvalidateBackgroundOrBorderPaintOnWidthChange() const { |
| if (HasMask() && |
| MustInvalidateFillLayersPaintOnWidthChange(Style()->MaskLayers())) |
| return true; |
| |
| // If we don't have a background/border/mask, then nothing to do. |
| if (!HasBoxDecorationBackground()) |
| return false; |
| |
| if (MustInvalidateFillLayersPaintOnWidthChange(Style()->BackgroundLayers())) |
| return true; |
| |
| // Our fill layers are ok. Let's check border. |
| if (Style()->CanRenderBorderImage()) |
| return true; |
| |
| return false; |
| } |
| |
| bool LayoutBox::MustInvalidateBackgroundOrBorderPaintOnHeightChange() const { |
| if (HasMask() && |
| MustInvalidateFillLayersPaintOnHeightChange(Style()->MaskLayers())) |
| return true; |
| |
| // If we don't have a background/border/mask, then nothing to do. |
| if (!HasBoxDecorationBackground()) |
| return false; |
| |
| if (MustInvalidateFillLayersPaintOnHeightChange(Style()->BackgroundLayers())) |
| return true; |
| |
| // Our fill layers are ok. Let's check border. |
| if (Style()->CanRenderBorderImage()) |
| return true; |
| |
| return false; |
| } |
| |
| ShapeOutsideInfo* LayoutBox::GetShapeOutsideInfo() const { |
| return ShapeOutsideInfo::IsEnabledFor(*this) ? ShapeOutsideInfo::Info(*this) |
| : nullptr; |
| } |
| |
| void LayoutBox::ClearPreviousVisualRects() { |
| LayoutBoxModelObject::ClearPreviousVisualRects(); |
| if (PaintLayerScrollableArea* scrollable_area = this->GetScrollableArea()) |
| scrollable_area->ClearPreviousVisualRects(); |
| } |
| |
| void LayoutBox::SetPercentHeightContainer(LayoutBlock* container) { |
| DCHECK(!container || !PercentHeightContainer()); |
| if (!container && !rare_data_) |
| return; |
| EnsureRareData().percent_height_container_ = container; |
| } |
| |
| void LayoutBox::RemoveFromPercentHeightContainer() { |
| if (!PercentHeightContainer()) |
| return; |
| |
| DCHECK(PercentHeightContainer()->HasPercentHeightDescendant(this)); |
| PercentHeightContainer()->RemovePercentHeightDescendant(this); |
| // The above call should call this object's |
| // setPercentHeightContainer(nullptr). |
| DCHECK(!PercentHeightContainer()); |
| } |
| |
| void LayoutBox::ClearPercentHeightDescendants() { |
| for (LayoutObject* curr = SlowFirstChild(); curr; |
| curr = curr->NextInPreOrder(this)) { |
| if (curr->IsBox()) |
| ToLayoutBox(curr)->RemoveFromPercentHeightContainer(); |
| } |
| } |
| |
| LayoutUnit LayoutBox::PageLogicalHeightForOffset(LayoutUnit offset) const { |
| // We need to have calculated some fragmentainer logical height (even a |
| // tentative one will do, though) in order to tell how tall one fragmentainer |
| // is. |
| DCHECK(IsPageLogicalHeightKnown()); |
| |
| LayoutView* layout_view = View(); |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| LayoutUnit page_logical_height; |
| if (!flow_thread) { |
| page_logical_height = layout_view->PageLogicalHeight(); |
| } else { |
| page_logical_height = flow_thread->PageLogicalHeightForOffset( |
| offset + OffsetFromLogicalTopOfFirstPage()); |
| } |
| DCHECK_GT(page_logical_height, LayoutUnit()); |
| return page_logical_height; |
| } |
| |
| bool LayoutBox::IsPageLogicalHeightKnown() const { |
| if (const LayoutFlowThread* flow_thread = FlowThreadContainingBlock()) |
| return flow_thread->IsPageLogicalHeightKnown(); |
| return View()->PageLogicalHeight(); |
| } |
| |
| LayoutUnit LayoutBox::PageRemainingLogicalHeightForOffset( |
| LayoutUnit offset, |
| PageBoundaryRule page_boundary_rule) const { |
| DCHECK(IsPageLogicalHeightKnown()); |
| LayoutView* layout_view = View(); |
| offset += OffsetFromLogicalTopOfFirstPage(); |
| |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| if (!flow_thread) { |
| LayoutUnit page_logical_height = layout_view->PageLogicalHeight(); |
| LayoutUnit remaining_height = |
| page_logical_height - IntMod(offset, page_logical_height); |
| if (page_boundary_rule == kAssociateWithFormerPage) { |
| // An offset exactly at a page boundary will act as being part of the |
| // former page in question (i.e. no remaining space), rather than being |
| // part of the latter (i.e. one whole page length of remaining space). |
| remaining_height = IntMod(remaining_height, page_logical_height); |
| } |
| return remaining_height; |
| } |
| |
| return flow_thread->PageRemainingLogicalHeightForOffset(offset, |
| page_boundary_rule); |
| } |
| |
| bool LayoutBox::CrossesPageBoundary(LayoutUnit offset, |
| LayoutUnit logical_height) const { |
| if (!IsPageLogicalHeightKnown()) |
| return false; |
| return PageRemainingLogicalHeightForOffset(offset, kAssociateWithLatterPage) < |
| logical_height; |
| } |
| |
| LayoutUnit LayoutBox::CalculatePaginationStrutToFitContent( |
| LayoutUnit offset, |
| LayoutUnit strut_to_next_page, |
| LayoutUnit content_logical_height) const { |
| DCHECK_EQ(strut_to_next_page, PageRemainingLogicalHeightForOffset( |
| offset, kAssociateWithLatterPage)); |
| // If we're inside a cell in a row that straddles a page then avoid the |
| // repeating header group if necessary. If we're a table section we're |
| // already accounting for it. |
| if (!IsTableSection()) { |
| LayoutState* layout_state = View()->GetLayoutState(); |
| strut_to_next_page += layout_state->HeightOffsetForTableHeaders(); |
| } |
| |
| LayoutUnit next_page_logical_top = offset + strut_to_next_page; |
| if (PageLogicalHeightForOffset(next_page_logical_top) >= |
| content_logical_height) |
| return strut_to_next_page; // Content fits just fine in the next page or |
| // column. |
| |
| // Moving to the top of the next page or column doesn't result in enough space |
| // for the content that we're trying to fit. If we're in a nested |
| // fragmentation context, we may find enough space if we move to a column |
| // further ahead, by effectively breaking to the next outer fragmentainer. |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| if (!flow_thread) { |
| // If there's no flow thread, we're not nested. All pages have the same |
| // height. Give up. |
| return strut_to_next_page; |
| } |
| // Start searching for a suitable offset at the top of the next page or |
| // column. |
| LayoutUnit flow_thread_offset = |
| OffsetFromLogicalTopOfFirstPage() + next_page_logical_top; |
| return strut_to_next_page + |
| flow_thread->NextLogicalTopForUnbreakableContent( |
| flow_thread_offset, content_logical_height) - |
| flow_thread_offset; |
| } |
| |
| LayoutBox* LayoutBox::SnapContainer() const { |
| return rare_data_ ? rare_data_->snap_container_ : nullptr; |
| } |
| |
| void LayoutBox::SetSnapContainer(LayoutBox* new_container) { |
| LayoutBox* old_container = SnapContainer(); |
| if (old_container == new_container) |
| return; |
| |
| if (old_container) |
| old_container->RemoveSnapArea(*this); |
| |
| EnsureRareData().snap_container_ = new_container; |
| |
| if (new_container) |
| new_container->AddSnapArea(*this); |
| } |
| |
| void LayoutBox::ClearSnapAreas() { |
| if (SnapAreaSet* areas = SnapAreas()) { |
| for (auto& snap_area : *areas) |
| snap_area->rare_data_->snap_container_ = nullptr; |
| areas->clear(); |
| } |
| } |
| |
| void LayoutBox::AddSnapArea(const LayoutBox& snap_area) { |
| EnsureRareData().EnsureSnapAreas().insert(&snap_area); |
| } |
| |
| void LayoutBox::RemoveSnapArea(const LayoutBox& snap_area) { |
| if (rare_data_ && rare_data_->snap_areas_) { |
| rare_data_->snap_areas_->erase(&snap_area); |
| } |
| } |
| |
| SnapAreaSet* LayoutBox::SnapAreas() const { |
| return rare_data_ ? rare_data_->snap_areas_.get() : nullptr; |
| } |
| |
| void LayoutBox::SetPendingOffsetToScroll(LayoutSize offset) { |
| EnsureRareData().pending_offset_to_scroll_ = offset; |
| } |
| |
| LayoutRect LayoutBox::DebugRect() const { |
| LayoutRect rect = FrameRect(); |
| |
| LayoutBlock* block = ContainingBlock(); |
| if (block) |
| block->AdjustChildDebugRect(rect); |
| |
| return rect; |
| } |
| |
| bool LayoutBox::ShouldClipOverflow() const { |
| return HasOverflowClip() || StyleRef().ContainsPaint() || HasControlClip(); |
| } |
| |
| void LayoutBox::MutableForPainting:: |
| SavePreviousContentBoxSizeAndLayoutOverflowRect() { |
| auto& rare_data = GetLayoutBox().EnsureRareData(); |
| rare_data.has_previous_content_box_size_and_layout_overflow_rect_ = true; |
| rare_data.previous_content_box_size_ = GetLayoutBox().ContentBoxRect().Size(); |
| rare_data.previous_layout_overflow_rect_ = |
| GetLayoutBox().LayoutOverflowRect(); |
| } |
| |
| } // namespace blink |