| /* |
| * 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 "third_party/blink/renderer/core/layout/layout_box.h" |
| |
| #include <math.h> |
| #include <algorithm> |
| #include "third_party/blink/public/platform/web_rect.h" |
| #include "third_party/blink/public/platform/web_scroll_into_view_params.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/editing/editing_utilities.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_client.h" |
| #include "third_party/blink/renderer/core/frame/local_frame_view.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/html/html_element.h" |
| #include "third_party/blink/renderer/core/html/html_frame_element_base.h" |
| #include "third_party/blink/renderer/core/html/html_frame_owner_element.h" |
| #include "third_party/blink/renderer/core/input/event_handler.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_box.h" |
| #include "third_party/blink/renderer/core/layout/custom/layout_custom.h" |
| #include "third_party/blink/renderer/core/layout/custom/layout_worklet.h" |
| #include "third_party/blink/renderer/core/layout/custom/layout_worklet_global_scope_proxy.h" |
| #include "third_party/blink/renderer/core/layout/hit_test_result.h" |
| #include "third_party/blink/renderer/core/layout/layout_analyzer.h" |
| #include "third_party/blink/renderer/core/layout/layout_deprecated_flexible_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" |
| #include "third_party/blink/renderer/core/layout/layout_fieldset.h" |
| #include "third_party/blink/renderer/core/layout/layout_flexible_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_grid.h" |
| #include "third_party/blink/renderer/core/layout/layout_inline.h" |
| #include "third_party/blink/renderer/core/layout/layout_list_marker.h" |
| #include "third_party/blink/renderer/core/layout/layout_multi_column_flow_thread.h" |
| #include "third_party/blink/renderer/core/layout/layout_multi_column_spanner_placeholder.h" |
| #include "third_party/blink/renderer/core/layout/layout_table_cell.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" |
| #include "third_party/blink/renderer/core/layout/shapes/shape_outside_info.h" |
| #include "third_party/blink/renderer/core/page/autoscroll_controller.h" |
| #include "third_party/blink/renderer/core/page/page.h" |
| #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h" |
| #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h" |
| #include "third_party/blink/renderer/core/paint/background_image_geometry.h" |
| #include "third_party/blink/renderer/core/paint/box_paint_invalidator.h" |
| #include "third_party/blink/renderer/core/paint/box_painter.h" |
| #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h" |
| #include "third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer.h" |
| #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" |
| #include "third_party/blink/renderer/core/style/shadow_list.h" |
| #include "third_party/blink/renderer/platform/geometry/double_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/float_quad.h" |
| #include "third_party/blink/renderer/platform/geometry/float_rounded_rect.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.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(); |
| } |
| |
| LayoutBox::~LayoutBox() { |
| #if DCHECK_IS_ON() |
| if (IsInLayoutNGInlineFormattingContext()) |
| DCHECK(!first_paint_fragment_); |
| #endif |
| } |
| |
| 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() || HasTransformRelatedProperty() || |
| HasHiddenBackface() || HasReflection() || StyleRef().SpecifiesColumns() || |
| StyleRef().IsStackingContext() || |
| StyleRef().ShouldCompositeForCurrentAnimations() || |
| IsEffectiveRootScroller()) |
| return kNormalPaintLayer; |
| |
| if (HasOverflowClip()) |
| return kOverflowClipPaintLayer; |
| |
| return kNoPaintLayer; |
| } |
| |
| void LayoutBox::WillBeDestroyed() { |
| ClearOverrideSize(); |
| ClearOverrideContainingBlockContentSize(); |
| ClearOverrideContainingBlockPercentageResolutionLogicalHeight(); |
| |
| if (IsOutOfFlowPositioned()) |
| LayoutBlock::RemovePositionedObject(this); |
| RemoveFromPercentHeightContainer(); |
| if (IsOrthogonalWritingModeRoot() && !DocumentBeingDestroyed()) |
| UnmarkOrthogonalWritingModeRoot(); |
| |
| ShapeOutsideInfo::RemoveInfo(*this); |
| |
| LayoutBoxModelObject::WillBeDestroyed(); |
| } |
| |
| void LayoutBox::InsertedIntoTree() { |
| LayoutBoxModelObject::InsertedIntoTree(); |
| AddScrollSnapMapping(); |
| AddCustomLayoutChildIfNeeded(); |
| |
| if (IsOrthogonalWritingModeRoot()) |
| MarkOrthogonalWritingModeRoot(); |
| } |
| |
| void LayoutBox::WillBeRemovedFromTree() { |
| if (!DocumentBeingDestroyed() && IsOrthogonalWritingModeRoot()) |
| UnmarkOrthogonalWritingModeRoot(); |
| |
| ClearCustomLayoutChild(); |
| ClearScrollSnapMapping(); |
| LayoutBoxModelObject::WillBeRemovedFromTree(); |
| } |
| |
| void LayoutBox::RemoveFloatingOrPositionedChildFromBlockLists() { |
| DCHECK(IsFloatingOrOutOfFlowPositioned()); |
| |
| if (DocumentBeingDestroyed()) |
| return; |
| |
| if (IsFloating()) { |
| LayoutBlockFlow* parent_block_flow = nullptr; |
| for (LayoutObject* curr = Parent(); curr; curr = curr->Parent()) { |
| if (curr->IsLayoutBlockFlow()) { |
| LayoutBlockFlow* curr_block_flow = ToLayoutBlockFlow(curr); |
| if (!parent_block_flow || curr_block_flow->ContainsFloat(this)) |
| parent_block_flow = curr_block_flow; |
| } |
| } |
| |
| if (parent_block_flow) { |
| parent_block_flow->MarkSiblingsWithFloatsForLayout(this); |
| parent_block_flow->MarkAllDescendantsWithFloatsForLayout(this, false); |
| } |
| } |
| |
| if (IsOutOfFlowPositioned()) |
| LayoutBlock::RemovePositionedObject(this); |
| } |
| |
| void LayoutBox::StyleWillChange(StyleDifference diff, |
| const ComputedStyle& new_style) { |
| const ComputedStyle* old_style = Style(); |
| if (old_style) { |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| if (flow_thread && flow_thread != this) |
| flow_thread->FlowThreadDescendantStyleWillChange(this, diff, new_style); |
| |
| // The background of the root element or the body element could propagate up |
| // to the canvas. Just dirty the entire canvas when our style changes |
| // substantially. |
| if ((diff.NeedsFullPaintInvalidation() || diff.NeedsLayout()) && |
| GetNode() && (IsDocumentElement() || IsHTMLBodyElement(*GetNode()))) { |
| View()->SetShouldDoFullPaintInvalidation(); |
| } |
| |
| // 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( |
| layout_invalidation_reason::kStyleChange); |
| } else { |
| MarkContainerChainForLayout(); |
| } |
| if (old_style->GetPosition() == EPosition::kStatic) |
| SetShouldDoFullPaintInvalidation(); |
| else if (new_style.HasOutOfFlowPosition()) |
| Parent()->SetChildNeedsLayout(); |
| if (IsFloating() && !IsOutOfFlowPositioned() && |
| new_style.HasOutOfFlowPosition()) |
| RemoveFloatingOrPositionedChildFromBlockLists(); |
| } |
| // FIXME: This branch runs when !oldStyle, which means that layout was never |
| // called so what's the point in invalidating the whole view that we never |
| // painted? |
| } else if (IsBody()) { |
| View()->SetShouldDoFullPaintInvalidation(); |
| } |
| |
| LayoutBoxModelObject::StyleWillChange(diff, new_style); |
| } |
| |
| void LayoutBox::StyleDidChange(StyleDifference diff, |
| const ComputedStyle* old_style) { |
| // Horizontal writing mode definition is updated in LayoutBoxModelObject:: |
| // updateFromStyle, (as part of the LayoutBoxModelObject::styleDidChange call |
| // below). So, we can safely cache the horizontal writing mode value before |
| // style change here. |
| bool old_horizontal_writing_mode = IsHorizontalWritingMode(); |
| |
| LayoutBoxModelObject::StyleDidChange(diff, old_style); |
| |
| // Reflection works through PaintLayer. Some child classes e.g. LayoutSVGBlock |
| // don't create layers and ignore reflections. |
| if (HasReflection() && !HasLayer()) |
| SetHasReflection(false); |
| |
| if (IsFloatingOrOutOfFlowPositioned() && old_style && |
| !old_style->IsFloating() && !old_style->HasOutOfFlowPosition() && |
| Parent() && Parent()->IsLayoutBlockFlow()) |
| ToLayoutBlockFlow(Parent())->ChildBecameFloatingOrOutOfFlow(this); |
| |
| const ComputedStyle& new_style = StyleRef(); |
| if (NeedsLayout() && old_style) |
| RemoveFromPercentHeightContainer(); |
| |
| if (old_horizontal_writing_mode != IsHorizontalWritingMode()) { |
| if (old_style) { |
| if (IsOrthogonalWritingModeRoot()) |
| MarkOrthogonalWritingModeRoot(); |
| else |
| UnmarkOrthogonalWritingModeRoot(); |
| } |
| |
| ClearPercentHeightDescendants(); |
| } |
| |
| SetShouldClipOverflow(ComputeShouldClipOverflow()); |
| |
| // If our zoom factor changes and we have a defined scrollLeft/Top, we need to |
| // adjust that value into the new zoomed coordinate space. Note that the new |
| // scroll offset may be outside the normal min/max range of the scrollable |
| // area, which is weird but OK, because the scrollable area will update its |
| // min/max in updateAfterLayout(). |
| if (HasOverflowClip() && old_style && |
| old_style->EffectiveZoom() != new_style.EffectiveZoom()) { |
| PaintLayerScrollableArea* scrollable_area = GetScrollableArea(); |
| DCHECK(scrollable_area); |
| // We use getScrollOffset() rather than scrollPosition(), because scroll |
| // offset is the distance from the beginning of flow for the box, which is |
| // the dimension we want to preserve. |
| ScrollOffset old_offset = scrollable_area->GetScrollOffset(); |
| if (old_offset.Width() || old_offset.Height()) { |
| ScrollOffset new_offset = old_offset.ScaledBy(new_style.EffectiveZoom() / |
| old_style->EffectiveZoom()); |
| scrollable_area->SetScrollOffsetUnconditionally(new_offset); |
| } |
| } |
| |
| // Our opaqueness might have changed without triggering layout. |
| if (diff.NeedsFullPaintInvalidation()) { |
| // Invalidate self. |
| InvalidateBackgroundObscurationStatus(); |
| LayoutObject* parent_to_invalidate = Parent(); |
| // Also invalidate up to kBackgroundObscurationTestMaxDepth parents. |
| // This constant corresponds to a descendant walk of the same depth; |
| // see ComputeBackgroundIsKnownToBeObscured. |
| for (unsigned i = 0; |
| i < kBackgroundObscurationTestMaxDepth && parent_to_invalidate; ++i) { |
| parent_to_invalidate->InvalidateBackgroundObscurationStatus(); |
| parent_to_invalidate = parent_to_invalidate->Parent(); |
| } |
| } |
| |
| UpdateShapeOutsideInfoAfterStyleChange(*Style(), old_style); |
| UpdateGridPositionAfterStyleChange(old_style); |
| |
| // When we're no longer a flex item because we're now absolutely positioned, |
| // we need to clear the override size so we're not affected by it anymore. |
| // This technically covers too many cases (even when out-of-flow did not |
| // change) but that should be harmless. |
| if (IsOutOfFlowPositioned() && Parent() && |
| Parent()->StyleRef().IsDisplayFlexibleOrGridBox()) |
| ClearOverrideSize(); |
| |
| if (LayoutMultiColumnSpannerPlaceholder* placeholder = SpannerPlaceholder()) |
| placeholder->LayoutObjectInFlowThreadStyleDidChange(old_style); |
| |
| UpdateBackgroundAttachmentFixedStatusAfterStyleChange(); |
| |
| if (old_style) { |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| if (flow_thread && flow_thread != this) |
| flow_thread->FlowThreadDescendantStyleDidChange(this, diff, *old_style); |
| |
| UpdateScrollSnapMappingAfterStyleChange(&new_style, old_style); |
| |
| if (ShouldClipOverflow()) { |
| // The overflow clip paint property depends on border sizes through |
| // overflowClipRect(), and border radii, so we update properties on |
| // border size or radii change. |
| if (!old_style->BorderSizeEquals(new_style) || |
| !old_style->RadiiEqual(new_style)) { |
| SetNeedsPaintPropertyUpdate(); |
| if (Layer()) |
| Layer()->SetNeedsCompositingInputsUpdate(); |
| } |
| } |
| |
| if (old_style->OverscrollBehaviorX() != new_style.OverscrollBehaviorX() || |
| old_style->OverscrollBehaviorY() != new_style.OverscrollBehaviorY()) { |
| SetNeedsPaintPropertyUpdate(); |
| } |
| } |
| |
| if (diff.TransformChanged()) { |
| if (ScrollingCoordinator* scrolling_coordinator = |
| GetDocument().GetFrame()->GetPage()->GetScrollingCoordinator()) { |
| if (LocalFrame* frame = GetFrame()) |
| scrolling_coordinator->NotifyTransformChanged(frame, *this); |
| } |
| } |
| |
| // Update the script style map, from the new computed style. |
| if (IsCustomItem()) |
| GetCustomLayoutChild()->styleMap()->UpdateStyle(GetDocument(), StyleRef()); |
| |
| // 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; |
| |
| SetIsBackgroundAttachmentFixedObject( |
| !BackgroundTransfersToView() && |
| StyleRef().HasFixedAttachmentBackgroundImage()); |
| } |
| |
| void LayoutBox::UpdateShapeOutsideInfoAfterStyleChange( |
| const ComputedStyle& style, |
| const ComputedStyle* old_style) { |
| const ShapeValue* shape_outside = style.ShapeOutside(); |
| const ShapeValue* old_shape_outside = |
| old_style ? old_style->ShapeOutside() |
| : ComputedStyleInitialValues::InitialShapeOutside(); |
| |
| Length shape_margin = style.ShapeMargin(); |
| Length old_shape_margin = |
| old_style ? old_style->ShapeMargin() |
| : ComputedStyleInitialValues::InitialShapeMargin(); |
| |
| float shape_image_threshold = style.ShapeImageThreshold(); |
| float old_shape_image_threshold = |
| old_style ? old_style->ShapeImageThreshold() |
| : ComputedStyleInitialValues::InitialShapeImageThreshold(); |
| |
| // FIXME: A future optimization would do a deep comparison for equality. (bug |
| // 100811) |
| if (shape_outside == old_shape_outside && shape_margin == old_shape_margin && |
| shape_image_threshold == old_shape_image_threshold) |
| return; |
| |
| if (!shape_outside) |
| ShapeOutsideInfo::RemoveInfo(*this); |
| else |
| ShapeOutsideInfo::EnsureInfo(*this).MarkShapeAsDirty(); |
| |
| if (shape_outside || shape_outside != old_shape_outside) |
| MarkShapeOutsideDependentsForLayout(); |
| } |
| |
| void LayoutBox::UpdateGridPositionAfterStyleChange( |
| const ComputedStyle* old_style) { |
| if (!old_style || !Parent() || !Parent()->IsLayoutGrid()) |
| return; |
| |
| if (old_style->GridColumnStart() == StyleRef().GridColumnStart() && |
| old_style->GridColumnEnd() == StyleRef().GridColumnEnd() && |
| old_style->GridRowStart() == StyleRef().GridRowStart() && |
| old_style->GridRowEnd() == StyleRef().GridRowEnd() && |
| old_style->Order() == StyleRef().Order() && |
| old_style->HasOutOfFlowPosition() == StyleRef().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() && StyleRef().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); |
| |
| if (LayoutBlockedByDisplayLock()) |
| return; |
| |
| LayoutObject* child = SlowFirstChild(); |
| if (!child) { |
| ClearNeedsLayout(); |
| return; |
| } |
| |
| LayoutState state(*this); |
| while (child) { |
| child->LayoutIfNeeded(); |
| DCHECK(!child->NeedsLayout()); |
| child = child->NextSibling(); |
| } |
| UpdateAfterLayout(); |
| ClearNeedsLayout(); |
| NotifyDisplayLockDidLayout(); |
| } |
| |
| // 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. This function may be called during layout |
| // before frame_rect_ gets the final proper value. 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() - |
| VerticalScrollbarWidthClampedToContentBox()) |
| .ClampNegativeToZero(); |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutUnit LayoutBox::ClientHeight() const { |
| // We need to clamp negative values. This function can be called during layout |
| // before frame_rect_ gets the final proper value. 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 (StyleRef().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); |
| } |
| |
| LayoutRect LayoutBox::ScrollRectToVisibleRecursive( |
| const LayoutRect& absolute_rect, |
| const WebScrollIntoViewParams& params) { |
| DCHECK(params.GetScrollType() == kProgrammaticScroll || |
| params.GetScrollType() == kUserScroll); |
| |
| if (!GetFrameView()) |
| return absolute_rect; |
| |
| if (params.stop_at_main_frame_layout_viewport && IsLayoutView() && |
| GetFrame()->IsMainFrame()) |
| return absolute_rect; |
| |
| // Presumably the same issue as in setScrollTop. See crbug.com/343132. |
| DisableCompositingQueryAsserts disabler; |
| |
| LayoutRect absolute_rect_to_scroll = absolute_rect; |
| if (absolute_rect_to_scroll.Width() <= 0) |
| absolute_rect_to_scroll.SetWidth(LayoutUnit(1)); |
| if (absolute_rect_to_scroll.Height() <= 0) |
| absolute_rect_to_scroll.SetHeight(LayoutUnit(1)); |
| |
| LayoutBox* parent_box = nullptr; |
| |
| if (ContainingBlock()) |
| parent_box = ContainingBlock(); |
| |
| LayoutRect absolute_rect_for_parent; |
| if (!IsLayoutView() && HasOverflowClip()) { |
| absolute_rect_for_parent = |
| GetScrollableArea()->ScrollIntoView(absolute_rect_to_scroll, params); |
| } else if (!parent_box && CanBeProgramaticallyScrolled()) { |
| ScrollableArea* area_to_scroll = params.make_visible_in_visual_viewport |
| ? GetFrameView()->GetScrollableArea() |
| : GetFrameView()->LayoutViewport(); |
| absolute_rect_for_parent = |
| area_to_scroll->ScrollIntoView(absolute_rect_to_scroll, params); |
| |
| // If the parent is a local iframe, convert to the absolute coordinate |
| // space of its document. For remote frames, this will happen on the other |
| // end of the IPC call. |
| HTMLFrameOwnerElement* owner_element = GetDocument().LocalOwner(); |
| if (owner_element && owner_element->GetLayoutObject() && |
| AllowedToPropagateRecursiveScrollToParentFrame(params)) { |
| parent_box = owner_element->GetLayoutObject()->EnclosingBox(); |
| LayoutView* parent_view = owner_element->GetLayoutObject()->View(); |
| absolute_rect_for_parent = EnclosingLayoutRect( |
| View() |
| ->LocalToAncestorQuad( |
| FloatRect(absolute_rect_for_parent), parent_view, |
| kUseTransforms | kTraverseDocumentBoundaries) |
| .BoundingBox()); |
| } |
| } else { |
| absolute_rect_for_parent = absolute_rect_to_scroll; |
| } |
| |
| // If we are fixed-position and stick to the viewport, it is useless to |
| // scroll the parent. |
| if (StyleRef().GetPosition() == EPosition::kFixed && Container() == View()) |
| return absolute_rect_for_parent; |
| |
| if (parent_box) { |
| return parent_box->ScrollRectToVisibleRecursive(absolute_rect_for_parent, |
| params); |
| } else if (GetFrame()->IsLocalRoot() && !GetFrame()->IsMainFrame()) { |
| if (AllowedToPropagateRecursiveScrollToParentFrame(params)) { |
| GetFrameView()->ScrollRectToVisibleInRemoteParent( |
| absolute_rect_for_parent, params); |
| } |
| } |
| |
| return absolute_rect_for_parent; |
| } |
| |
| void LayoutBox::SetMargin(const NGPhysicalBoxStrut& box) { |
| margin_box_outsets_.SetTop(box.top); |
| margin_box_outsets_.SetRight(box.right); |
| margin_box_outsets_.SetBottom(box.bottom); |
| margin_box_outsets_.SetLeft(box.left); |
| } |
| |
| void LayoutBox::AbsoluteRects(Vector<IntRect>& rects, |
| const LayoutPoint& accumulated_offset) const { |
| rects.push_back(PixelSnappedIntRect(accumulated_offset, Size())); |
| } |
| |
| void LayoutBox::AbsoluteQuads(Vector<FloatQuad>& quads, |
| MapCoordinatesFlags mode) const { |
| if (LayoutFlowThread* flow_thread = FlowThreadContainingBlock()) { |
| flow_thread->AbsoluteQuadsForDescendant(*this, quads, mode); |
| return; |
| } |
| quads.push_back( |
| LocalToAbsoluteQuad(FloatRect(0, 0, frame_rect_.Width().ToFloat(), |
| frame_rect_.Height().ToFloat()), |
| mode)); |
| } |
| |
| FloatRect LayoutBox::LocalBoundingBoxRectForAccessibility() const { |
| return FloatRect(0, 0, frame_rect_.Width().ToFloat(), |
| frame_rect_.Height().ToFloat()); |
| } |
| |
| void LayoutBox::UpdateAfterLayout() { |
| InvalidateBackgroundObscurationStatus(); |
| |
| // 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 (StyleRef().IsHorizontalWritingMode()) |
| return overflow.MaxY(); |
| return overflow.MaxX(); |
| } |
| |
| LayoutUnit LayoutBox::ConstrainLogicalWidthByMinMax( |
| LayoutUnit logical_width, |
| LayoutUnit available_width, |
| const 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(); |
| } |
| } |
| |
| FloatQuad LayoutBox::AbsoluteContentQuad(MapCoordinatesFlags flags) const { |
| LayoutRect rect = PhysicalContentBoxRect(); |
| return LocalToAbsoluteQuad(FloatRect(rect), flags); |
| } |
| |
| LayoutRect LayoutBox::PhysicalBackgroundRect( |
| BackgroundRectType rect_type) const { |
| EFillBox background_box = EFillBox::kText; |
| // Find the largest background rect of the given opaqueness. |
| if (const FillLayer* current = &(StyleRef().BackgroundLayers())) { |
| do { |
| const FillLayer* cur = current; |
| current = current->Next(); |
| if (rect_type == kBackgroundKnownOpaqueRect) { |
| if (cur->GetBlendMode() != BlendMode::kNormal || |
| cur->Composite() != kCompositeSourceOver) |
| continue; |
| |
| bool layer_known_opaque = false; |
| // Check if the image is opaque and fills the clip. |
| if (const StyleImage* image = cur->GetImage()) { |
| if ((cur->RepeatX() == EFillRepeat::kRepeatFill || |
| cur->RepeatX() == EFillRepeat::kRoundFill) && |
| (cur->RepeatY() == EFillRepeat::kRepeatFill || |
| cur->RepeatY() == EFillRepeat::kRoundFill) && |
| image->KnownToBeOpaque(GetDocument(), StyleRef())) { |
| layer_known_opaque = true; |
| } |
| } |
| |
| // The background color is painted into the last layer. |
| if (!cur->Next()) { |
| Color background_color = |
| ResolveColor(GetCSSPropertyBackgroundColor()); |
| if (!background_color.HasAlpha()) |
| layer_known_opaque = true; |
| } |
| |
| // If neither the image nor the color are opaque then skip this layer. |
| if (!layer_known_opaque) |
| continue; |
| } |
| EFillBox current_clip = cur->Clip(); |
| // Restrict clip if attachment is local. |
| if (current_clip == EFillBox::kBorder && |
| cur->Attachment() == EFillAttachment::kLocal) |
| current_clip = EFillBox::kPadding; |
| |
| // If we're asking for the clip rect, a content-box clipped fill layer can |
| // be scrolled into the padding box of the overflow container. |
| if (rect_type == kBackgroundClipRect && |
| current_clip == EFillBox::kContent && |
| cur->Attachment() == EFillAttachment::kLocal) { |
| current_clip = EFillBox::kPadding; |
| } |
| |
| background_box = EnclosingFillBox(background_box, current_clip); |
| } while (current); |
| } |
| switch (background_box) { |
| case EFillBox::kBorder: |
| return BorderBoxRect(); |
| break; |
| case EFillBox::kPadding: |
| return PhysicalPaddingBoxRect(); |
| break; |
| case EFillBox::kContent: |
| return PhysicalContentBoxRect(); |
| break; |
| default: |
| break; |
| } |
| return LayoutRect(); |
| } |
| |
| void LayoutBox::AddOutlineRects(Vector<LayoutRect>& rects, |
| const LayoutPoint& additional_offset, |
| NGOutlineType) 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()) && |
| StyleRef().Resize() != EResize::kNone; |
| } |
| |
| void LayoutBox::AddLayerHitTestRects( |
| LayerHitTestRects& layer_rects, |
| const PaintLayer* current_layer, |
| const LayoutPoint& layer_offset, |
| TouchAction supported_fast_actions, |
| const LayoutRect& container_rect, |
| TouchAction container_whitelisted_touch_action) const { |
| LayoutPoint adjusted_layer_offset = layer_offset + LocationOffset(); |
| LayoutBoxModelObject::AddLayerHitTestRects( |
| layer_rects, current_layer, adjusted_layer_offset, supported_fast_actions, |
| container_rect, container_whitelisted_touch_action); |
| } |
| |
| void LayoutBox::ComputeSelfHitTestRects(Vector<LayoutRect>& rects, |
| const LayoutPoint& layer_offset) const { |
| if (!Size().IsEmpty()) |
| rects.push_back(LayoutRect(layer_offset, Size())); |
| } |
| |
| int LayoutBox::VerticalScrollbarWidth() const { |
| if (!HasOverflowClip() || StyleRef().OverflowY() == EOverflow::kOverlay) |
| return 0; |
| |
| return GetScrollableArea()->VerticalScrollbarWidth(); |
| } |
| |
| int LayoutBox::HorizontalScrollbarHeight() const { |
| if (!HasOverflowClip() || StyleRef().OverflowX() == EOverflow::kOverlay) |
| return 0; |
| |
| return GetScrollableArea()->HorizontalScrollbarHeight(); |
| } |
| |
| LayoutUnit LayoutBox::VerticalScrollbarWidthClampedToContentBox() const { |
| LayoutUnit width(VerticalScrollbarWidth()); |
| DCHECK_GE(width, LayoutUnit()); |
| if (width) { |
| LayoutUnit maximum_width = LogicalWidth() - BorderAndPaddingLogicalWidth(); |
| width = std::min(width, maximum_width.ClampNegativeToZero()); |
| } |
| return width; |
| } |
| |
| bool LayoutBox::CanBeScrolledAndHasScrollableArea() const { |
| return CanBeProgramaticallyScrolled() && |
| (PixelSnappedScrollHeight() != PixelSnappedClientHeight() || |
| PixelSnappedScrollWidth() != PixelSnappedClientWidth()); |
| } |
| |
| bool LayoutBox::CanBeProgramaticallyScrolled() const { |
| Node* node = GetNode(); |
| if (node && node->IsDocumentNode()) |
| return true; |
| |
| if (!HasOverflowClip()) |
| return false; |
| |
| bool has_scrollable_overflow = |
| HasScrollableOverflowX() || HasScrollableOverflowY(); |
| if (ScrollsOverflow() && has_scrollable_overflow) |
| return true; |
| |
| return node && HasEditableStyle(*node); |
| } |
| |
| void LayoutBox::Autoscroll(const IntPoint& position_in_root_frame) { |
| LocalFrame* frame = GetFrame(); |
| if (!frame) |
| return; |
| |
| LocalFrameView* frame_view = frame->View(); |
| if (!frame_view) |
| return; |
| |
| IntPoint absolute_position = |
| frame_view->ConvertFromRootFrame(position_in_root_frame); |
| ScrollRectToVisibleRecursive( |
| LayoutRect(absolute_position, LayoutSize(1, 1)), |
| WebScrollIntoViewParams(ScrollAlignment::kAlignToEdgeIfNeeded, |
| ScrollAlignment::kAlignToEdgeIfNeeded, |
| kUserScroll)); |
| } |
| |
| bool LayoutBox::CanAutoscroll() const { |
| // TODO(skobes): Remove one of these methods. |
| return CanBeScrolledAndHasScrollableArea(); |
| } |
| |
| // If specified point is outside the border-belt-excluded box (the border box |
| // inset by the autoscroll activation threshold), returned offset denotes |
| // direction of scrolling. |
| IntSize LayoutBox::CalculateAutoscrollDirection( |
| const IntPoint& point_in_root_frame) const { |
| if (!GetFrame()) |
| return IntSize(); |
| |
| LocalFrameView* frame_view = GetFrame()->View(); |
| if (!frame_view) |
| return IntSize(); |
| |
| LayoutRect absolute_scrolling_box = LayoutRect(AbsoluteBoundingBoxRect()); |
| |
| // Exclude scrollbars so the border belt (activation area) starts from the |
| // scrollbar-content edge rather than the window edge. |
| ExcludeScrollbars(absolute_scrolling_box, |
| kExcludeOverlayScrollbarSizeForHitTesting); |
| |
| IntRect belt_box = View()->GetFrameView()->ConvertToRootFrame( |
| PixelSnappedIntRect(absolute_scrolling_box)); |
| belt_box.Inflate(-kAutoscrollBeltSize); |
| IntPoint point = point_in_root_frame; |
| |
| if (point.X() < belt_box.X()) |
| point.Move(-kAutoscrollBeltSize, 0); |
| else if (point.X() > belt_box.MaxX()) |
| point.Move(kAutoscrollBeltSize, 0); |
| |
| if (point.Y() < belt_box.Y()) |
| point.Move(0, -kAutoscrollBeltSize); |
| else if (point.Y() > belt_box.MaxY()) |
| point.Move(0, kAutoscrollBeltSize); |
| |
| return 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::MayUpdateHoverWhenContentUnderMouseChanged( |
| EventHandler& event_handler) { |
| const LayoutBoxModelObject& container = ContainerForPaintInvalidation(); |
| FloatQuad scroller_rect_in_frame = |
| FloatQuad(FloatRect(VisualRectIncludingCompositedScrolling(container))); |
| scroller_rect_in_frame = |
| container.LocalToAbsoluteQuad(scroller_rect_in_frame); |
| event_handler.MayUpdateHoverAfterScroll(scroller_rect_in_frame); |
| } |
| |
| void LayoutBox::ScrollByRecursively(const ScrollOffset& delta) { |
| if (delta.IsZero() || !HasOverflowClip()) |
| return; |
| |
| PaintLayerScrollableArea* scrollable_area = GetScrollableArea(); |
| DCHECK(scrollable_area); |
| |
| ScrollOffset new_scroll_offset = scrollable_area->GetScrollOffset() + delta; |
| scrollable_area->SetScrollOffset(new_scroll_offset, kProgrammaticScroll); |
| |
| // If this layer can't do the scroll we ask the next layer up that can |
| // scroll to try. |
| ScrollOffset remaining_scroll_offset = |
| new_scroll_offset - scrollable_area->GetScrollOffset(); |
| if (!remaining_scroll_offset.IsZero() && Parent()) { |
| if (LayoutBox* scrollable_box = EnclosingScrollableBox()) |
| scrollable_box->ScrollByRecursively(remaining_scroll_offset); |
| |
| LocalFrame* frame = GetFrame(); |
| if (frame && frame->GetPage()) { |
| frame->GetPage() |
| ->GetAutoscrollController() |
| .UpdateAutoscrollLayoutObject(); |
| } |
| } |
| // 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 StyleRef().PaddingStart().IsPercentOrCalc() || |
| StyleRef().PaddingEnd().IsPercentOrCalc(); |
| } |
| |
| IntSize LayoutBox::OriginAdjustmentForScrollbars() const { |
| return IntSize(LeftScrollbarWidth().ToInt(), 0); |
| } |
| |
| IntPoint LayoutBox::ScrollOrigin() const { |
| return GetScrollableArea() ? GetScrollableArea()->ScrollOrigin() : IntPoint(); |
| } |
| |
| IntSize LayoutBox::ScrolledContentOffset() const { |
| DCHECK(HasOverflowClip()); |
| DCHECK(GetScrollableArea()); |
| // FIXME: Return DoubleSize here. crbug.com/414283. |
| return GetScrollableArea()->ScrollOffsetInt(); |
| } |
| |
| LayoutRect LayoutBox::ClippingRect(const LayoutPoint& location) const { |
| LayoutRect result = LayoutRect(LayoutRect::InfiniteIntRect()); |
| if (ShouldClipOverflow()) |
| result = OverflowClipRect(location); |
| |
| if (HasClip()) |
| result.Intersect(ClipRect(location)); |
| |
| return result; |
| } |
| |
| bool LayoutBox::MapVisualRectToContainer( |
| const LayoutObject* container_object, |
| const LayoutPoint& container_offset, |
| const LayoutObject* ancestor, |
| VisualRectFlags visual_rect_flags, |
| TransformState& transform_state) const { |
| bool container_preserve_3d = container_object->StyleRef().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) |
| ->MapContentsRectToBoxSpace(transform_state, accumulation, *this, |
| 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 && |
| ToLayoutBox(container_object)->ContainedContentsScroll(*this)) { |
| 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->StyleRef().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->StyleRef().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::MapContentsRectToBoxSpace( |
| TransformState& transform_state, |
| TransformState::TransformAccumulation accumulation, |
| const LayoutObject& contents, |
| VisualRectFlags visual_rect_flags) const { |
| if (!HasClipRelatedProperty()) |
| return true; |
| |
| if (ContainedContentsScroll(contents)) { |
| LayoutSize offset = LayoutSize(-ScrolledContentOffset()); |
| transform_state.Move(offset, accumulation); |
| } |
| |
| return ApplyBoxClips(transform_state, accumulation, visual_rect_flags); |
| } |
| |
| bool LayoutBox::ContainedContentsScroll(const LayoutObject& contents) const { |
| if (IsLayoutView() && |
| contents.StyleRef().GetPosition() == EPosition::kFixed) { |
| return false; |
| } |
| return HasOverflowClip(); |
| } |
| |
| bool LayoutBox::ApplyBoxClips( |
| TransformState& transform_state, |
| TransformState::TransformAccumulation accumulation, |
| VisualRectFlags visual_rect_flags) const { |
| // This won't work fully correctly for fixed-position elements, who should |
| // receive CSS clip but for whom the current object is not in the containing |
| // block chain. |
| LayoutRect clip_rect = ClippingRect(LayoutPoint()); |
| |
| transform_state.Flatten(); |
| LayoutRect rect(transform_state.LastPlanarQuad().EnclosingBoundingBox()); |
| bool does_intersect; |
| if (visual_rect_flags & kEdgeInclusive) { |
| does_intersect = rect.InclusiveIntersect(clip_rect); |
| } else { |
| rect.Intersect(clip_rect); |
| does_intersect = !rect.IsEmpty(); |
| } |
| transform_state.SetQuad(FloatQuad(FloatRect(rect))); |
| |
| return does_intersect; |
| } |
| |
| void LayoutBox::ComputeIntrinsicLogicalWidths( |
| LayoutUnit& min_logical_width, |
| LayoutUnit& max_logical_width) const { |
| min_logical_width = |
| MinPreferredLogicalWidth() - BorderAndPaddingLogicalWidth(); |
| max_logical_width = |
| MaxPreferredLogicalWidth() - BorderAndPaddingLogicalWidth(); |
| } |
| |
| LayoutUnit LayoutBox::MinPreferredLogicalWidth() const { |
| if (PreferredLogicalWidthsDirty()) { |
| #if DCHECK_IS_ON() |
| SetLayoutNeededForbiddenScope layout_forbidden_scope( |
| const_cast<LayoutBox&>(*this)); |
| #endif |
| const_cast<LayoutBox*>(this)->ComputePreferredLogicalWidths(); |
| DCHECK(!PreferredLogicalWidthsDirty()); |
| } |
| |
| return min_preferred_logical_width_; |
| } |
| |
| DISABLE_CFI_PERF |
| LayoutUnit LayoutBox::MaxPreferredLogicalWidth() const { |
| if (PreferredLogicalWidthsDirty()) { |
| #if DCHECK_IS_ON() |
| SetLayoutNeededForbiddenScope layout_forbidden_scope( |
| const_cast<LayoutBox&>(*this)); |
| #endif |
| const_cast<LayoutBox*>(this)->ComputePreferredLogicalWidths(); |
| DCHECK(!PreferredLogicalWidthsDirty()); |
| } |
| |
| return max_preferred_logical_width_; |
| } |
| |
| LayoutUnit LayoutBox::OverrideLogicalWidth() const { |
| DCHECK(HasOverrideLogicalWidth()); |
| return rare_data_->override_logical_width_; |
| } |
| |
| LayoutUnit LayoutBox::OverrideLogicalHeight() const { |
| DCHECK(HasOverrideLogicalHeight()); |
| return rare_data_->override_logical_height_; |
| } |
| |
| bool LayoutBox::HasOverrideLogicalHeight() const { |
| return rare_data_ && rare_data_->override_logical_height_ != -1; |
| } |
| |
| bool LayoutBox::HasOverrideLogicalWidth() const { |
| return rare_data_ && rare_data_->override_logical_width_ != -1; |
| } |
| |
| void LayoutBox::SetOverrideLogicalHeight(LayoutUnit height) { |
| DCHECK_GE(height, 0); |
| EnsureRareData().override_logical_height_ = height; |
| } |
| |
| void LayoutBox::SetOverrideLogicalWidth(LayoutUnit width) { |
| DCHECK_GE(width, 0); |
| EnsureRareData().override_logical_width_ = width; |
| } |
| |
| void LayoutBox::ClearOverrideLogicalHeight() { |
| if (rare_data_) |
| rare_data_->override_logical_height_ = LayoutUnit(-1); |
| } |
| |
| void LayoutBox::ClearOverrideLogicalWidth() { |
| if (rare_data_) |
| rare_data_->override_logical_width_ = LayoutUnit(-1); |
| } |
| |
| void LayoutBox::ClearOverrideSize() { |
| ClearOverrideLogicalHeight(); |
| ClearOverrideLogicalWidth(); |
| } |
| |
| LayoutUnit LayoutBox::OverrideContentLogicalWidth() const { |
| return (OverrideLogicalWidth() - BorderAndPaddingLogicalWidth() - |
| ScrollbarLogicalWidth()) |
| .ClampNegativeToZero(); |
| } |
| |
| LayoutUnit LayoutBox::OverrideContentLogicalHeight() const { |
| return (OverrideLogicalHeight() - BorderAndPaddingLogicalHeight() - |
| ScrollbarLogicalHeight()) |
| .ClampNegativeToZero(); |
| } |
| |
| LayoutUnit LayoutBox::OverrideContainingBlockContentWidth() const { |
| DCHECK(HasOverrideContainingBlockContentWidth()); |
| return ContainingBlock()->StyleRef().IsHorizontalWritingMode() |
| ? rare_data_->override_containing_block_content_logical_width_ |
| : rare_data_->override_containing_block_content_logical_height_; |
| } |
| |
| LayoutUnit LayoutBox::OverrideContainingBlockContentHeight() const { |
| DCHECK(HasOverrideContainingBlockContentHeight()); |
| return ContainingBlock()->StyleRef().IsHorizontalWritingMode() |
| ? rare_data_->override_containing_block_content_logical_height_ |
| : rare_data_->override_containing_block_content_logical_width_; |
| } |
| |
| bool LayoutBox::HasOverrideContainingBlockContentWidth() const { |
| if (!rare_data_ || !ContainingBlock()) |
| return false; |
| |
| return ContainingBlock()->StyleRef().IsHorizontalWritingMode() |
| ? rare_data_->has_override_containing_block_content_logical_width_ |
| : rare_data_ |
| ->has_override_containing_block_content_logical_height_; |
| } |
| |
| bool LayoutBox::HasOverrideContainingBlockContentHeight() const { |
| if (!rare_data_ || !ContainingBlock()) |
| return false; |
| |
| return ContainingBlock()->StyleRef().IsHorizontalWritingMode() |
| ? rare_data_->has_override_containing_block_content_logical_height_ |
| : rare_data_->has_override_containing_block_content_logical_width_; |
| } |
| |
| // TODO (lajava) Shouldn't we implement these functions based on physical |
| // direction ?. |
| LayoutUnit LayoutBox::OverrideContainingBlockContentLogicalWidth() const { |
| DCHECK(HasOverrideContainingBlockContentLogicalWidth()); |
| 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(HasOverrideContainingBlockContentLogicalHeight()); |
| return rare_data_->override_containing_block_content_logical_height_; |
| } |
| |
| // TODO (lajava) Shouldn't we implement these functions based on physical |
| // direction ?. |
| bool LayoutBox::HasOverrideContainingBlockContentLogicalWidth() 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::HasOverrideContainingBlockContentLogicalHeight() 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::ClearOverrideContainingBlockContentSize() { |
| if (!rare_data_) |
| return; |
| EnsureRareData().has_override_containing_block_content_logical_width_ = false; |
| EnsureRareData().has_override_containing_block_content_logical_height_ = |
| false; |
| } |
| |
| LayoutUnit LayoutBox::OverrideContainingBlockPercentageResolutionLogicalHeight() |
| const { |
| DCHECK(HasOverrideContainingBlockPercentageResolutionLogicalHeight()); |
| return rare_data_ |
| ->override_containing_block_percentage_resolution_logical_height_; |
| } |
| |
| bool LayoutBox::HasOverrideContainingBlockPercentageResolutionLogicalHeight() |
| const { |
| return rare_data_ && |
| rare_data_ |
| ->has_override_containing_block_percentage_resolution_logical_height_; |
| } |
| |
| void LayoutBox::SetOverrideContainingBlockPercentageResolutionLogicalHeight( |
| LayoutUnit logical_height) { |
| DCHECK_GE(logical_height, LayoutUnit(-1)); |
| EnsureRareData() |
| .override_containing_block_percentage_resolution_logical_height_ = |
| logical_height; |
| EnsureRareData() |
| .has_override_containing_block_percentage_resolution_logical_height_ = |
| true; |
| } |
| |
| void LayoutBox:: |
| ClearOverrideContainingBlockPercentageResolutionLogicalHeight() { |
| if (!rare_data_) |
| return; |
| EnsureRareData() |
| .has_override_containing_block_percentage_resolution_logical_height_ = |
| false; |
| } |
| |
| LayoutUnit LayoutBox::AdjustBorderBoxLogicalWidthForBoxSizing( |
| float width) const { |
| LayoutUnit borders_plus_padding = CollapsedBorderAndCSSPaddingLogicalWidth(); |
| LayoutUnit result(width); |
| if (StyleRef().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 (StyleRef().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 (StyleRef().BoxSizing() == EBoxSizing::kBorderBox) |
| result -= CollapsedBorderAndCSSPaddingLogicalWidth(); |
| return std::max(LayoutUnit(), result); |
| } |
| |
| LayoutUnit LayoutBox::AdjustContentBoxLogicalHeightForBoxSizing( |
| float height) const { |
| LayoutUnit result(height); |
| if (StyleRef().BoxSizing() == EBoxSizing::kBorderBox) |
| result -= CollapsedBorderAndCSSPaddingLogicalHeight(); |
| return std::max(LayoutUnit(), result); |
| } |
| |
| // Hit Testing |
| bool LayoutBox::HitTestAllPhases(HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestFilter hit_test_filter) { |
| // Check if we need to do anything at all. |
| // If we have clipping, then we can't have any spillout. |
| // TODO(pdr): Why is this optimization not valid for the effective root? |
| if (!IsEffectiveRootScroller()) { |
| LayoutRect overflow_box = |
| (HasOverflowClip() || ShouldApplyPaintContainment()) |
| ? BorderBoxRect() |
| : VisualOverflowRect(); |
| FlipForWritingMode(overflow_box); |
| LayoutPoint adjusted_location = accumulated_offset + Location(); |
| overflow_box.MoveBy(adjusted_location); |
| if (!location_in_container.Intersects(overflow_box)) |
| return false; |
| } |
| return LayoutObject::HitTestAllPhases(result, location_in_container, |
| accumulated_offset, hit_test_filter); |
| } |
| |
| bool LayoutBox::NodeAtPoint(HitTestResult& result, |
| const HitTestLocation& location_in_container, |
| const LayoutPoint& accumulated_offset, |
| HitTestAction action) { |
| LayoutPoint adjusted_location = accumulated_offset + Location(); |
| |
| bool should_hit_test_self = IsInSelfHitTestingPhase(action); |
| |
| if (should_hit_test_self && HasOverflowClip() && |
| HitTestOverflowControl(result, location_in_container, adjusted_location)) |
| return true; |
| |
| bool skip_children = (result.GetHitTestRequest().GetStopNode() == this); |
| if (!skip_children && ShouldClipOverflow()) { |
| // PaintLayer::HitTestContentsForFragments checked the fragments' |
| // foreground rect for intersection if a layer is self painting, |
| // so only do the overflow clip check here for non-self-painting layers. |
| if (!HasSelfPaintingLayer() && |
| !location_in_container.Intersects(OverflowClipRect( |
| adjusted_location, kExcludeOverlayScrollbarSizeForHitTesting))) { |
| skip_children = true; |
| } |
| if (!skip_children && StyleRef().HasBorderRadius()) { |
| LayoutRect bounds_rect(adjusted_location, Size()); |
| skip_children = !location_in_container.Intersects( |
| StyleRef().GetRoundedInnerBorderFor(bounds_rect)); |
| } |
| } |
| |
| if (!skip_children && HitTestChildren(result, location_in_container, |
| adjusted_location, action)) { |
| return true; |
| } |
| |
| if (StyleRef().HasBorderRadius() && |
| HitTestClippedOutByBorder(location_in_container, adjusted_location)) |
| return false; |
| |
| // Now hit test ourselves. |
| if (should_hit_test_self && |
| VisibleToHitTestRequest(result.GetHitTestRequest())) { |
| LayoutRect bounds_rect; |
| if (result.GetHitTestRequest().GetType() & |
| HitTestRequest::kHitTestVisualOverflow) { |
| bounds_rect = VisualOverflowRect(); |
| } else { |
| bounds_rect = BorderBoxRect(); |
| } |
| bounds_rect.Move(ToSize(adjusted_location)); |
| 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( |
| StyleRef().GetRoundedBorderFor(border_rect)); |
| } |
| |
| void LayoutBox::Paint(const PaintInfo& paint_info) const { |
| BoxPainter(*this).Paint(paint_info); |
| } |
| |
| void LayoutBox::PaintBoxDecorationBackground( |
| const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) const { |
| BoxPainter(*this).PaintBoxDecorationBackground(paint_info, paint_offset); |
| } |
| |
| bool LayoutBox::GetBackgroundPaintedExtent(LayoutRect& painted_extent) const { |
| DCHECK(StyleRef().HasBackground()); |
| |
| // LayoutView is special in the sense that it expands to the whole canvas, |
| // thus can't be handled by this function. |
| DCHECK(!IsLayoutView()); |
| |
| LayoutRect background_rect(BorderBoxRect()); |
| |
| Color background_color = ResolveColor(GetCSSPropertyBackgroundColor()); |
| if (background_color.Alpha()) { |
| painted_extent = background_rect; |
| return true; |
| } |
| |
| if (!StyleRef().BackgroundLayers().GetImage() || |
| StyleRef().BackgroundLayers().Next()) { |
| painted_extent = background_rect; |
| return true; |
| } |
| |
| BackgroundImageGeometry geometry(*this); |
| // TODO(schenney): This function should be rethought as it's called during |
| // and outside of the paint phase. Potentially returning different results at |
| // different phases. crbug.com/732934 |
| geometry.Calculate(nullptr, PaintPhase::kBlockBackground, |
| kGlobalPaintNormalPhase, StyleRef().BackgroundLayers(), |
| background_rect); |
| if (geometry.HasNonLocalGeometry()) |
| return false; |
| painted_extent = LayoutRect(geometry.SnappedDestRect()); |
| return true; |
| } |
| |
| bool LayoutBox::BackgroundIsKnownToBeOpaqueInRect( |
| const LayoutRect& local_rect) const { |
| // If the background transfers to view, the used background of this object |
| // is transparent. |
| if (BackgroundTransfersToView()) |
| 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 (StyleRef().HasAppearance()) |
| return false; |
| // FIXME: Check the opaqueness of background images. |
| |
| // FIXME: Use rounded rect if border radius is present. |
| if (StyleRef().HasBorderRadius()) |
| return false; |
| if (HasClipPath()) |
| return false; |
| if (StyleRef().HasBlendMode()) |
| return false; |
| return PhysicalBackgroundRect(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; |
| // CSS clip is not considered in foreground or background opaqueness checks. |
| if (child_box.HasClip()) |
| 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 (StyleRef().BoxShadow()) |
| return false; |
| LayoutRect background_rect; |
| if (!GetBackgroundPaintedExtent(background_rect)) |
| return false; |
| return ForegroundIsKnownToBeOpaqueInRect(background_rect, |
| kBackgroundObscurationTestMaxDepth); |
| } |
| |
| void LayoutBox::PaintMask(const PaintInfo& paint_info, |
| const LayoutPoint& paint_offset) const { |
| BoxPainter(*this).PaintMask(paint_info, paint_offset); |
| } |
| |
| void LayoutBox::ImageChanged(WrappedImagePtr image, |
| CanDeferInvalidation defer) { |
| bool is_box_reflect_image = |
| (StyleRef().BoxReflect() && StyleRef().BoxReflect()->Mask().GetImage() && |
| StyleRef().BoxReflect()->Mask().GetImage()->Data() == image); |
| |
| if (is_box_reflect_image && HasLayer()) { |
| Layer()->SetFilterOnEffectNodeDirty(); |
| SetNeedsPaintPropertyUpdate(); |
| } |
| |
| // TODO(chrishtr): support delayed paint invalidation for animated border |
| // images. |
| if ((StyleRef().BorderImage().GetImage() && |
| StyleRef().BorderImage().GetImage()->Data() == image) || |
| (StyleRef().MaskBoxImage().GetImage() && |
| StyleRef().MaskBoxImage().GetImage()->Data() == image) || |
| is_box_reflect_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 (!BackgroundTransfersToView()) { |
| for (const FillLayer* layer = &StyleRef().BackgroundLayers(); layer; |
| layer = layer->Next()) { |
| if (layer->GetImage() && image == layer->GetImage()->Data()) { |
| InvalidateBackgroundObscurationStatus(); |
| bool maybe_animated = |
| layer->GetImage()->CachedImage() && |
| layer->GetImage()->CachedImage()->GetImage() && |
| layer->GetImage()->CachedImage()->GetImage()->MaybeAnimated(); |
| if (defer == CanDeferInvalidation::kYes && maybe_animated) |
| SetMayNeedPaintInvalidationAnimatedBackgroundImage(); |
| else |
| SetBackgroundNeedsFullPaintInvalidation(); |
| break; |
| } |
| } |
| } |
| |
| ShapeValue* shape_outside_value = StyleRef().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 = PhysicalContentBoxRect(); |
| object_bounds.MoveBy(LayoutPoint(LocalToAbsolute())); |
| |
| // 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()) |
| SetShouldCheckForPaintInvalidation(); |
| } |
| |
| void LayoutBox::SizeChanged() { |
| // The size may change because of layout of other objects. Should check this |
| // object for paint invalidation. |
| if (!NeedsLayout()) |
| SetShouldCheckForPaintInvalidation(); |
| |
| if (GetNode() && GetNode()->IsElementNode()) { |
| Element& element = ToElement(*GetNode()); |
| element.SetNeedsResizeObserverUpdate(); |
| } |
| } |
| |
| bool LayoutBox::IntersectsVisibleViewport() const { |
| LayoutRect rect = VisualOverflowRect(); |
| LayoutView* layout_view = View(); |
| while (layout_view->GetFrame()->OwnerLayoutObject()) |
| layout_view = layout_view->GetFrame()->OwnerLayoutObject()->View(); |
| MapToVisualRectInAncestorSpace(layout_view, rect); |
| return rect.Intersects(LayoutRect( |
| layout_view->GetFrameView()->GetScrollableArea()->VisibleContentRect())); |
| } |
| |
| void LayoutBox::EnsureIsReadyForPaintInvalidation() { |
| LayoutBoxModelObject::EnsureIsReadyForPaintInvalidation(); |
| |
| if (MayNeedPaintInvalidationAnimatedBackgroundImage() && |
| !BackgroundIsKnownToBeObscured()) { |
| SetBackgroundNeedsFullPaintInvalidation(); |
| SetShouldDelayFullPaintInvalidation(); |
| } |
| |
| if (!ShouldDelayFullPaintInvalidation() || !IntersectsVisibleViewport()) |
| return; |
| |
| // Do regular full paint invalidation if the object with delayed paint |
| // invalidation is onscreen. This will clear |
| // ShouldDelayFullPaintInvalidation() flag and enable previous |
| // BackgroundNeedsFullPaintInvalidaiton() if it's set. |
| SetShouldDoFullPaintInvalidationWithoutGeometryChange( |
| FullPaintInvalidationReason()); |
| } |
| |
| void LayoutBox::InvalidatePaint(const PaintInvalidatorContext& context) const { |
| BoxPaintInvalidator(*this, context).InvalidatePaint(); |
| } |
| |
| LayoutRect LayoutBox::OverflowClipRect( |
| const LayoutPoint& location, |
| OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const { |
| LayoutRect clip_rect; |
| |
| if (IsEffectiveRootScroller()) { |
| // If this box is the effective root scroller, use the viewport clipping |
| // rect since it will account for the URL bar correctly which the border |
| // box does not. We can do this because the effective root scroller is |
| // restricted such that it exactly fills the viewport. See |
| // RootScrollerController::IsValidRootScroller() |
| clip_rect = LayoutRect(location, View()->ViewRect().Size()); |
| } else { |
| // FIXME: When overflow-clip (CSS3) is implemented, we'll obtain the |
| // property here. |
| clip_rect = BorderBoxRect(); |
| clip_rect.SetLocation(location + clip_rect.Location() + |
| LayoutSize(BorderLeft(), BorderTop())); |
| clip_rect.SetSize(clip_rect.Size() - |
| LayoutSize(BorderWidth(), BorderHeight())); |
| } |
| |
| if (HasOverflowClip()) |
| ExcludeScrollbars(clip_rect, overlay_scrollbar_clip_behavior); |
| |
| if (HasControlClip()) |
| clip_rect.Intersect(ControlClipRect(location)); |
| |
| return clip_rect; |
| } |
| |
| void LayoutBox::ExcludeScrollbars( |
| LayoutRect& rect, |
| OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const { |
| if (PaintLayerScrollableArea* scrollable_area = GetScrollableArea()) { |
| if (ShouldPlaceBlockDirectionScrollbarOnLogicalLeft()) { |
| rect.Move(scrollable_area->VerticalScrollbarWidth( |
| overlay_scrollbar_clip_behavior), |
| 0); |
| } |
| rect.Contract(scrollable_area->VerticalScrollbarWidth( |
| overlay_scrollbar_clip_behavior), |
| scrollable_area->HorizontalScrollbarHeight( |
| overlay_scrollbar_clip_behavior)); |
| } |
| } |
| |
| LayoutRect LayoutBox::ClipRect(const LayoutPoint& location) const { |
| LayoutRect border_box_rect = BorderBoxRect(); |
| LayoutRect clip_rect = |
| LayoutRect(border_box_rect.Location() + location, border_box_rect.Size()); |
| |
| if (!StyleRef().ClipLeft().IsAuto()) { |
| LayoutUnit c = |
| ValueForLength(StyleRef().ClipLeft(), border_box_rect.Width()); |
| clip_rect.Move(c, LayoutUnit()); |
| clip_rect.Contract(c, LayoutUnit()); |
| } |
| |
| if (!StyleRef().ClipRight().IsAuto()) |
| clip_rect.Contract( |
| Size().Width() - ValueForLength(StyleRef().ClipRight(), Size().Width()), |
| LayoutUnit()); |
| |
| if (!StyleRef().ClipTop().IsAuto()) { |
| LayoutUnit c = |
| ValueForLength(StyleRef().ClipTop(), border_box_rect.Height()); |
| clip_rect.Move(LayoutUnit(), c); |
| clip_rect.Contract(LayoutUnit(), c); |
| } |
| |
| if (!StyleRef().ClipBottom().IsAuto()) { |
| clip_rect.Contract(LayoutUnit(), |
| Size().Height() - ValueForLength(StyleRef().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(); |
| |
| // NOTE: This call to LogicalHeightForChild is bad, as it may contain data |
| // from a previous layout. |
| LayoutUnit logical_height = cb->LogicalHeightForChild(*this); |
| LayoutUnit start_offset_for_avoiding_floats = |
| cb->StartOffsetForAvoidingFloats(logical_top_position, logical_height); |
| LayoutUnit end_offset_for_avoiding_floats = |
| cb->EndOffsetForAvoidingFloats(logical_top_position, 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_avoiding_floats && |
| end_offset_for_content == end_offset_for_avoiding_floats) |
| return cb->AvailableLogicalWidthForAvoidingFloats(logical_top_position, |
| logical_height) - |
| child_margin_start - child_margin_end; |
| |
| LayoutUnit width = cb->AvailableLogicalWidthForAvoidingFloats( |
| logical_top_position, logical_height); |
| width -= std::max(LayoutUnit(), child_margin_start); |
| width -= std::max(LayoutUnit(), child_margin_end); |
| |
| // We need to see if margins on either the start side or the end side can |
| // contain the floats in question. If they can, then just using the line width |
| // is inaccurate. In the case where a float completely fits, we don't need to |
| // use the line offset at all, but can instead push all the way to the content |
| // edge of the containing block. In the case where the float doesn't fit, we |
| // can use the line offset, but we need to grow it by the margin to reflect |
| // the fact that the margin was "consumed" by the float. Negative margins |
| // aren't consumed by the float, and so we ignore them. |
| width += PortionOfMarginNotConsumedByFloat(child_margin_start, |
| start_offset_for_content, |
| start_offset_for_avoiding_floats); |
| width += PortionOfMarginNotConsumedByFloat( |
| child_margin_end, end_offset_for_content, end_offset_for_avoiding_floats); |
| return width; |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockLogicalHeightForGetComputedStyle() const { |
| if (HasOverrideContainingBlockContentLogicalHeight()) |
| return OverrideContainingBlockContentLogicalHeight(); |
| |
| if (!IsPositioned()) |
| return ContainingBlockLogicalHeightForContent(kExcludeMarginBorderPadding); |
| |
| LayoutBoxModelObject* cb = ToLayoutBoxModelObject(Container()); |
| LayoutUnit height = ContainingBlockLogicalHeightForPositioned( |
| cb, /* check_for_perpendicular_writing_mode */ false); |
| if (IsInFlowPositioned()) |
| height -= cb->PaddingLogicalHeight(); |
| return height; |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockLogicalWidthForContent() const { |
| if (HasOverrideContainingBlockContentLogicalWidth()) |
| return OverrideContainingBlockContentLogicalWidth(); |
| |
| LayoutBlock* cb = ContainingBlock(); |
| if (IsOutOfFlowPositioned()) |
| return cb->ClientLogicalWidth(); |
| return cb->AvailableLogicalWidth(); |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockLogicalHeightForContent( |
| AvailableLogicalHeightType height_type) const { |
| if (HasOverrideContainingBlockContentLogicalHeight()) |
| return OverrideContainingBlockContentLogicalHeight(); |
| |
| LayoutBlock* cb = ContainingBlock(); |
| return cb->AvailableLogicalHeight(height_type); |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockAvailableLineWidth() const { |
| LayoutBlock* cb = ContainingBlock(); |
| if (cb->IsLayoutBlockFlow()) { |
| return ToLayoutBlockFlow(cb)->AvailableLogicalWidthForAvoidingFloats( |
| LogicalTop(), AvailableLogicalHeight(kIncludeMarginBorderPadding)); |
| } |
| return LayoutUnit(); |
| } |
| |
| LayoutUnit LayoutBox::PerpendicularContainingBlockLogicalHeight() const { |
| if (HasOverrideContainingBlockContentLogicalHeight()) |
| return OverrideContainingBlockContentLogicalHeight(); |
| |
| LayoutBlock* cb = ContainingBlock(); |
| if (cb->HasOverrideLogicalHeight()) |
| return cb->OverrideContentLogicalHeight(); |
| |
| 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()->Size().Height() |
| : View()->GetFrameView()->Size().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 = StyleRef().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 = StyleRef().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::OffsetFromContainerInternal( |
| const LayoutObject* o, |
| bool ignore_scroll_offset) const { |
| DCHECK_EQ(o, Container()); |
| |
| LayoutSize offset; |
| if (IsInFlowPositioned()) |
| offset += OffsetForInFlowPosition(); |
| |
| offset += PhysicalLocationOffset(); |
| |
| if (o->HasOverflowClip()) |
| offset += OffsetFromScrollableContainer(o, ignore_scroll_offset); |
| |
| if (IsOutOfFlowPositioned() && o->IsLayoutInline() && |
| o->CanContainOutOfFlowPositionedElement(StyleRef().GetPosition())) { |
| offset += ToLayoutInline(o)->OffsetForInFlowPositionedInline(*this); |
| } |
| |
| return offset; |
| } |
| |
| InlineBox* LayoutBox::CreateInlineBox() { |
| return new InlineBox(LineLayoutItem(this)); |
| } |
| |
| void LayoutBox::DirtyLineBoxes(bool full_layout) { |
| if (IsInLayoutNGInlineFormattingContext()) { |
| SetFirstInlineFragment(nullptr); |
| } else if (inline_box_wrapper_) { |
| if (full_layout) { |
| inline_box_wrapper_->Destroy(); |
| inline_box_wrapper_ = nullptr; |
| } else { |
| inline_box_wrapper_->DirtyLineBoxes(); |
| } |
| } |
| } |
| |
| void LayoutBox::SetFirstInlineFragment(NGPaintFragment* fragment) { |
| CHECK(IsInLayoutNGInlineFormattingContext()) << *this; |
| first_paint_fragment_ = fragment; |
| } |
| |
| void LayoutBox::InLayoutNGInlineFormattingContextWillChange(bool new_value) { |
| DeleteLineBoxWrapper(); |
| |
| // Because |first_paint_fragment_| and |inline_box_wrapper_| are union, when |
| // one is deleted, the other should be initialized to nullptr. |
| DCHECK(new_value ? !first_paint_fragment_ : !inline_box_wrapper_); |
| } |
| |
| void LayoutBox::PositionLineBox(InlineBox* box) { |
| if (IsOutOfFlowPositioned()) { |
| // Cache the x position only if we were an INLINE type originally. |
| bool originally_inline = StyleRef().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()->CanContainOutOfFlowPositionedElement( |
| StyleRef().GetPosition())); |
| // 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() && |
| !StyleRef().HasStaticInlinePosition(is_horizontal)) |
| SetChildNeedsLayout(kMarkOnlyThis); |
| } |
| |
| void LayoutBox::DeleteLineBoxWrapper() { |
| if (IsInLayoutNGInlineFormattingContext()) { |
| SetFirstInlineFragment(nullptr); |
| } else 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 = StyleRef().BreakAfter(); |
| if (break_value == EBreakBetween::kAuto || |
| IsBreakBetweenControllable(break_value)) |
| return break_value; |
| return EBreakBetween::kAuto; |
| } |
| |
| EBreakBetween LayoutBox::BreakBefore() const { |
| EBreakBetween break_value = StyleRef().BreakBefore(); |
| if (break_value == EBreakBetween::kAuto || |
| IsBreakBetweenControllable(break_value)) |
| return break_value; |
| return EBreakBetween::kAuto; |
| } |
| |
| EBreakInside LayoutBox::BreakInside() const { |
| EBreakInside break_value = StyleRef().BreakInside(); |
| if (break_value == EBreakInside::kAuto || |
| IsBreakInsideControllable(break_value)) |
| return break_value; |
| return EBreakInside::kAuto; |
| } |
| |
| EBreakBetween LayoutBox::ClassABreakPointValue( |
| EBreakBetween previous_break_after_value) const { |
| // First assert that we're at a class A break point. |
| DCHECK(IsBreakBetweenControllable(previous_break_after_value)); |
| |
| return JoinFragmentainerBreakValues(previous_break_after_value, |
| BreakBefore()); |
| } |
| |
| bool LayoutBox::NeedsForcedBreakBefore( |
| EBreakBetween previous_break_after_value) const { |
| // Forced break values are only honored when specified on in-flow objects, but |
| // floats and out-of-flow positioned objects may be affected by a break-after |
| // value of the previous in-flow object, even though we're not at a class A |
| // break point. |
| EBreakBetween break_value = |
| IsFloatingOrOutOfFlowPositioned() |
| ? previous_break_after_value |
| : ClassABreakPointValue(previous_break_after_value); |
| return IsForcedFragmentainerBreakValue(break_value); |
| } |
| |
| bool LayoutBox::PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const { |
| if (HasNonCompositedScrollbars() || IsSelected() || |
| 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 paints into its own backing, we can assume that it's painting |
| // may have some effect. For example, honoring the border-radius clip on |
| // a composited child paints into a mask for an otherwise non-painting |
| // element, because children of that element will require the mask. |
| if (HasLayer() && Layer()->GetCompositingState() == kPaintsIntoOwnBacking) |
| return false; |
| |
| return true; |
| } |
| |
| LayoutRect LayoutBox::LocalVisualRectIgnoringVisibility() const { |
| return SelfVisualOverflowRect(); |
| } |
| |
| void LayoutBox::InflateVisualRectForFilterUnderContainer( |
| TransformState& transform_state, |
| const LayoutObject& container, |
| const LayoutBoxModelObject* ancestor_to_stop_at) const { |
| transform_state.Flatten(); |
| // Apply visual overflow caused by reflections and filters defined on objects |
| // between this object and container (not included) or ancestorToStopAt |
| // (included). |
| LayoutSize offset_from_container = OffsetFromContainer(&container); |
| transform_state.Move(offset_from_container); |
| for (LayoutObject* parent = Parent(); parent && parent != container; |
| parent = parent->Parent()) { |
| if (parent->IsBox()) { |
| // Convert rect into coordinate space of parent to apply parent's |
| // reflection and filter. |
| LayoutSize parent_offset = |
| parent->OffsetFromAncestor(&container); |
| transform_state.Move(-parent_offset); |
| ToLayoutBox(parent)->InflateVisualRectForFilter(transform_state); |
| transform_state.Move(parent_offset); |
| } |
| if (parent == ancestor_to_stop_at) |
| break; |
| } |
| transform_state.Move(-offset_from_container); |
| } |
| |
| bool LayoutBox::MapToVisualRectInAncestorSpaceInternal( |
| const LayoutBoxModelObject* ancestor, |
| TransformState& transform_state, |
| VisualRectFlags visual_rect_flags) const { |
| InflateVisualRectForFilter(transform_state); |
| |
| if (ancestor == this) |
| return true; |
| |
| AncestorSkipInfo skip_info(ancestor, true); |
| LayoutObject* container = Container(&skip_info); |
| LayoutBox* table_row_container = nullptr; |
| // Skip table row because cells and rows are in the same coordinate space (see |
| // below, however for more comments about when |ancestor| is the table row). |
| if (IsTableCell()) { |
| DCHECK(container->IsTableRow()); |
| DCHECK_EQ(ParentBox(), container); |
| if (container != ancestor) |
| container = container->Parent(); |
| else |
| table_row_container = ToLayoutBox(container); |
| } |
| if (!container) |
| return true; |
| |
| LayoutPoint container_offset; |
| if (container->IsBox()) { |
| container_offset.MoveBy(PhysicalLocation(ToLayoutBox(container))); |
| |
| // If the row is the ancestor, however, add its offset back in. In effect, |
| // this passes from the joint <td> / <tr> coordinate space to the parent |
| // space, then back to <tr> / <td>. |
| if (table_row_container) { |
| container_offset.MoveBy( |
| -table_row_container->PhysicalLocation(ToLayoutBox(container))); |
| } |
| } else if (container->IsRuby()) { |
| // TODO(wkorman): Generalize Ruby specialization and/or document more |
| // clearly. See the accompanying specialization in |
| // LayoutInline::mapToVisualRectInAncestorSpaceInternal. |
| container_offset.MoveBy(PhysicalLocation()); |
| } else { |
| container_offset.MoveBy(Location()); |
| } |
| |
| const ComputedStyle& style_to_use = StyleRef(); |
| EPosition position = style_to_use.GetPosition(); |
| if (IsOutOfFlowPositioned() && container->IsLayoutInline() && |
| container->CanContainOutOfFlowPositionedElement(position)) { |
| 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->StyleRef().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->OffsetFromAncestor(container); |
| transform_state.Move(-container_offset, accumulation); |
| return true; |
| } |
| |
| if (container->IsLayoutView()) { |
| bool use_fixed_position_adjustment = |
| position == EPosition::kFixed && container == ancestor; |
| return ToLayoutView(container)->MapToVisualRectInAncestorSpaceInternal( |
| ancestor, transform_state, use_fixed_position_adjustment ? 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->StyleRef().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_ = |
| ShouldApplySizeContainment() |
| ? BorderAndPaddingLogicalWidth() + ScrollbarLogicalWidth() |
| : 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 (HasOverrideLogicalWidth()) { |
| computed_values.extent_ = OverrideLogicalWidth(); |
| 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()->StyleRef().BoxOrient() == EBoxOrient::kVertical); |
| bool stretching = |
| (Parent()->StyleRef().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) { |
| // PerpendicularContainingBlockLogicalHeight() can return -1 in some |
| // situations but we cannot have a negative width, that's why we clamp it to |
| // zero. |
| container_width_in_inline_direction = |
| PerpendicularContainingBlockLogicalHeight().ClampNegativeToZero(); |
| } |
| |
| // 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_, |
| StyleRef().MarginStart(), StyleRef().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->IsFlexibleBoxIncludingDeprecatedAndNG() && !cb->IsLayoutGrid()) { |
| LayoutUnit new_margin_total = |
| container_logical_width - computed_values.extent_; |
| bool has_inverted_direction = cb->StyleRef().IsLeftToRightDirection() != |
| StyleRef().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->StyleRef().IsLeftToRightDirection() != |
| StyleRef().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); |
| |
| bool isOrthogonalElement = |
| IsHorizontalWritingMode() != ContainingBlock()->IsHorizontalWritingMode(); |
| LayoutUnit available_size_for_resolving_margin = |
| isOrthogonalElement ? ContainingBlockLogicalWidthForContent() |
| : available_logical_width; |
| margin_start = MinimumValueForLength(StyleRef().MarginStart(), |
| available_size_for_resolving_margin); |
| margin_end = MinimumValueForLength(StyleRef().MarginEnd(), |
| available_size_for_resolving_margin); |
| 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) { |
| if (!IsHTMLMarqueeElement(GetNode())) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kCSSFillAvailableLogicalWidth); |
| } |
| 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() == ItemPosition::kStretch; |
| } |
| |
| bool LayoutBox::IsStretchingColumnFlexItem() const { |
| LayoutObject* parent = Parent(); |
| if (parent->IsDeprecatedFlexibleBox() && |
| parent->StyleRef().BoxOrient() == EBoxOrient::kVertical && |
| parent->StyleRef().BoxAlign() == EBoxAlignment::kStretch) |
| return true; |
| |
| // We don't stretch multiline flexboxes because they need to apply line |
| // spacing (align-content) first. |
| if (parent->IsFlexibleBoxIncludingNG() && |
| parent->StyleRef().FlexWrap() == EFlexWrap::kNowrap && |
| parent->StyleRef().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() == ItemPosition::kStretch; |
| } |
| return style |
| .ResolvedJustifySelf(cb->SelfAlignmentNormalBehavior(this), |
| cb->Style()) |
| .GetPosition() == ItemPosition::kStretch; |
| } |
| |
| 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()->IsFlexibleBoxIncludingNG()) { |
| // For multiline columns, we need to apply align-content first, so we can't |
| // stretch now. |
| if (!Parent()->StyleRef().IsColumnFlexDirection() || |
| Parent()->StyleRef().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()->StyleRef().BoxOrient() == EBoxOrient::kHorizontal || |
| Parent()->StyleRef().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; |
| |
| if (IsCustomItem()) |
| return IsCustomItemShrinkToFit(); |
| |
| 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->IsFlexibleBoxIncludingNG()) { |
| // 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 https://drafts.csswg.org/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 + (!StyleRef().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() { |
| if (!HasOverrideLogicalHeight()) { |
| // If we have an override height, our children will have sized themselves |
| // relative to our override height, which would make our intrinsic size |
| // incorrect (too big). |
| 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() |
| ->StyleRef() |
| .LogicalHeight(); |
| } |
| |
| void LayoutBox::ComputeLogicalHeight( |
| LogicalExtentComputedValues& computed_values) const { |
| LayoutUnit height = |
| ShouldApplySizeContainment() |
| ? BorderAndPaddingLogicalHeight() + ScrollbarLogicalHeight() |
| : 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); |
| if (HasOverrideLogicalHeight()) |
| computed_values.extent_ = OverrideLogicalHeight(); |
| } 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_, StyleRef().MarginBefore(), |
| StyleRef().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()->StyleRef().BoxOrient() == EBoxOrient::kHorizontal; |
| bool stretching = |
| Parent()->StyleRef().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 (HasOverrideLogicalHeight()) { |
| h = Length(OverrideLogicalHeight(), kFixed); |
| } else if (treat_as_replaced) { |
| h = Length( |
| ComputeReplacedLogicalHeight() + BorderAndPaddingLogicalHeight(), |
| kFixed); |
| } else { |
| h = StyleRef().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(), |
| kFixed); |
| check_min_max_height = false; |
| } |
| |
| LayoutUnit height_result; |
| if (check_min_max_height) { |
| height_result = ComputeLogicalHeightUsing( |
| kMainOrPreferredSize, StyleRef().LogicalHeight(), |
| computed_values.extent_ - BorderAndPaddingLogicalHeight()); |
| if (height_result == -1) |
| height_result = computed_values.extent_; |
| height_result = ConstrainLogicalHeightByMinMax( |
| height_result, |
| computed_values.extent_ - BorderAndPaddingLogicalHeight()); |
| } else { |
| DCHECK(h.IsFixed()); |
| height_result = LayoutUnit(h.Value()); |
| } |
| |
| computed_values.extent_ = height_result; |
| ComputeMarginsForDirection( |
| flow_direction, cb, ContainingBlockLogicalWidthForContent(), |
| computed_values.extent_, computed_values.margins_.before_, |
| computed_values.margins_.after_, StyleRef().MarginBefore(), |
| StyleRef().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()) { |
| if (!IsHTMLMarqueeElement(GetNode())) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kCSSFillAvailableLogicalHeight); |
| } |
| 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 StyleRef().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->IsLayoutCustom() && |
| ToLayoutCustom(containing_block)->IsLoaded()) && |
| !containing_block |
| ->HasOverrideContainingBlockPercentageResolutionLogicalHeight() && |
| !containing_block->IsLayoutGrid() && |
| containing_block->StyleRef().LogicalHeight().IsAuto(); |
| } |
| |
| LayoutUnit LayoutBox::ContainingBlockLogicalHeightForPercentageResolution( |
| LayoutBlock** out_cb, |
| bool* out_skipped_auto_height_containing_block) const { |
| LayoutBlock* cb = ContainingBlock(); |
| const LayoutBlock* const real_cb = cb; |
| 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()) && |
| !HasOverrideContainingBlockContentLogicalHeight()) |
| root_margin_border_padding_height += cb->MarginBefore() + |
| cb->MarginAfter() + |
| cb->BorderAndPaddingLogicalHeight(); |
| skipped_auto_height_containing_block = true; |
| containing_block_child = cb; |
| cb = cb->ContainingBlock(); |
| } |
| |
| if (out_cb) |
| *out_cb = cb; |
| |
| if (out_skipped_auto_height_containing_block) { |
| *out_skipped_auto_height_containing_block = |
| skipped_auto_height_containing_block; |
| } |
| |
| LayoutUnit available_height(-1); |
| if (containing_block_child |
| ->HasOverrideContainingBlockPercentageResolutionLogicalHeight()) { |
| available_height = |
| containing_block_child |
| ->OverrideContainingBlockPercentageResolutionLogicalHeight(); |
| } else if ( |
| cb->HasOverrideContainingBlockPercentageResolutionLogicalHeight()) { |
| available_height = |
| cb->OverrideContainingBlockPercentageResolutionLogicalHeight(); |
| } else if (HasOverrideContainingBlockContentLogicalWidth() && |
| IsHorizontalWritingMode() != real_cb->IsHorizontalWritingMode()) { |
| available_height = OverrideContainingBlockContentLogicalWidth(); |
| } else if (HasOverrideContainingBlockContentLogicalHeight() && |
| IsHorizontalWritingMode() == real_cb->IsHorizontalWritingMode()) { |
| available_height = OverrideContainingBlockContentLogicalHeight(); |
| } else if (IsHorizontalWritingMode() != cb->IsHorizontalWritingMode()) { |
| available_height = |
| containing_block_child->ContainingBlockLogicalWidthForContent(); |
| } 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->HasOverrideLogicalHeight()) { |
| // 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 (StyleRef().OverflowY() != EOverflow::kVisible && |
| StyleRef().OverflowY() != EOverflow::kHidden && |
| !ShouldBeConsideredAsReplaced() && |
| (!cell->StyleRef().LogicalHeight().IsAuto() || |
| !cell->Table()->StyleRef().LogicalHeight().IsAuto())) |
| return LayoutUnit(); |
| return LayoutUnit(-1); |
| } |
| available_height = cb->OverrideLogicalHeight() - |
| cb->CollapsedBorderAndCSSPaddingLogicalHeight() - |
| cb->ScrollbarLogicalHeight(); |
| } |
| } 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(); |
| |
| return available_height; |
| } |
| |
| LayoutUnit LayoutBox::ComputePercentageLogicalHeight( |
| const Length& height) const { |
| bool skipped_auto_height_containing_block = false; |
| LayoutBlock* cb = nullptr; |
| LayoutUnit available_height = |
| ContainingBlockLogicalHeightForPercentageResolution( |
| &cb, &skipped_auto_height_containing_block); |
| |
| DCHECK(cb); |
| cb->AddPercentHeightDescendant(const_cast<LayoutBox*>(this)); |
| |
| if (available_height == -1) |
| return available_height; |
| |
| LayoutUnit result = ValueForLength(height, available_height); |
| |
| // |OverrideLogicalHeight| 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 |
| // |available_height| (given by |OverrideLogicalHeight|) to arrive |
| // at the child's computed height. |
| bool subtract_border_and_padding = |
| IsTable() || |
| (cb->IsTableCell() && !skipped_auto_height_containing_block && |
| cb->HasOverrideLogicalHeight() && |
| StyleRef().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, |
| StyleRef().LogicalWidth()), |
| should_compute_preferred); |
| } |
| |
| LayoutUnit LayoutBox::ComputeReplacedLogicalWidthRespectingMinMaxWidth( |
| LayoutUnit logical_width, |
| ShouldComputePreferred should_compute_preferred) const { |
| LayoutUnit min_logical_width = |
| (should_compute_preferred == kComputePreferred && |
| StyleRef().LogicalMinWidth().IsPercentOrCalc()) |
| ? logical_width |
| : ComputeReplacedLogicalWidthUsing(kMinSize, |
| StyleRef().LogicalMinWidth()); |
| LayoutUnit max_logical_width = |
| (should_compute_preferred == kComputePreferred && |
| StyleRef().LogicalMaxWidth().IsPercentOrCalc()) || |
| StyleRef().LogicalMaxWidth().IsMaxSizeNone() |
| ? logical_width |
| : ComputeReplacedLogicalWidthUsing(kMaxSize, |
| StyleRef().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: { |
| LayoutUnit cw; |
| if (IsOutOfFlowPositioned()) { |
| cw = ContainingBlockLogicalWidthForPositioned( |
| ToLayoutBoxModelObject(Container())); |
| } else { |
| cw = IsHorizontalWritingMode() == |
| ContainingBlock()->IsHorizontalWritingMode() |
| ? ContainingBlockLogicalWidthForContent() |
| : PerpendicularContainingBlockLogicalHeight(); |
| } |
| Length container_logical_width = |
| ContainingBlock()->StyleRef().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, |
| StyleRef().LogicalHeight())); |
| } |
| |
| bool LayoutBox::LogicalHeightComputesAsNone(SizeType size_type) const { |
| DCHECK(size_type == kMinSize || size_type == kMaxSize); |
| Length logical_height = size_type == kMinSize ? StyleRef().LogicalMinHeight() |
| : StyleRef().LogicalMaxHeight(); |
| Length initial_logical_height = |
| size_type == kMinSize ? ComputedStyleInitialValues::InitialMinHeight() |
| : ComputedStyleInitialValues::InitialMaxHeight(); |
| |
| if (logical_height == initial_logical_height) |
| return true; |
| |
| // CustomLayout items can resolve their percentages against an available or |
| // percentage size override. |
| if (IsCustomItem() && |
| (HasOverrideContainingBlockContentLogicalHeight() || |
| HasOverrideContainingBlockPercentageResolutionLogicalHeight())) |
| return false; |
| |
| 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, StyleRef().LogicalMinHeight()); |
| } |
| LayoutUnit max_logical_height = logical_height; |
| if (!LogicalHeightComputesAsNone(kMaxSize)) { |
| max_logical_height = ComputeReplacedLogicalHeightUsing( |
| kMaxSize, StyleRef().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(); |
| bool has_perpendicular_containing_block = |
| cb->IsHorizontalWritingMode() != IsHorizontalWritingMode(); |
| LayoutUnit stretched_height(-1); |
| if (cb->IsLayoutBlock()) { |
| LayoutBlock* block = ToLayoutBlock(cb); |
| block->AddPercentHeightDescendant(const_cast<LayoutBox*>(this)); |
| if (block->IsFlexItem()) { |
| const LayoutFlexibleBox* flex_box = |
| ToLayoutFlexibleBox(block->Parent()); |
| if (flex_box->UseOverrideLogicalHeightForPerentageResolution(*block)) |
| stretched_height = block->OverrideContentLogicalHeight(); |
| } else if (block->IsGridItem() && block->HasOverrideLogicalHeight() && |
| !has_perpendicular_containing_block) { |
| stretched_height = block->OverrideContentLogicalHeight(); |
| } |
| } |
| |
| LayoutUnit available_height; |
| if (IsOutOfFlowPositioned()) { |
| available_height = ContainingBlockLogicalHeightForPositioned( |
| ToLayoutBoxModelObject(cb)); |
| } else if (stretched_height != -1) { |
| available_height = stretched_height; |
| } else if ( |
| HasOverrideContainingBlockPercentageResolutionLogicalHeight()) { |
| available_height = |
| OverrideContainingBlockPercentageResolutionLogicalHeight(); |
| } else { |
| available_height = has_perpendicular_containing_block |
| ? ContainingBlockLogicalWidthForContent() |
| : 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->StyleRef().LogicalHeight().IsAuto() || |
| cb->StyleRef().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 { |
| if (RuntimeEnabledFeatures::LayoutNGEnabled()) { |
| // LayoutNG code is correct, Legacy code incorrectly ConstrainsMinMax |
| // when height is -1, and returns 0, not -1. |
| // The reason this code is NG-only is that this code causes performance |
| // regression for nested-percent-height-tables test case. |
| // This code gets executed 740 times in the test case. |
| // https://chromium-review.googlesource.com/c/chromium/src/+/1103289 |
| LayoutUnit height = |
| AvailableLogicalHeightUsing(StyleRef().LogicalHeight(), height_type); |
| if (UNLIKELY(height == -1)) |
| return height; |
| return ConstrainContentBoxLogicalHeightByMinMax(height, LayoutUnit(-1)); |
| } |
| // 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(StyleRef().LogicalHeight(), height_type), |
| LayoutUnit(-1)); |
| } |
| |
| LayoutUnit LayoutBox::AvailableLogicalHeightUsing( |
| const Length& h, |
| AvailableLogicalHeightType height_type) const { |
| if (IsLayoutView()) { |
| return LayoutUnit(IsHorizontalWritingMode() |
| ? ToLayoutView(this)->GetFrameView()->Size().Height() |
| : ToLayoutView(this)->GetFrameView()->Size().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 (HasOverrideLogicalHeight()) { |
| return OverrideLogicalHeight() - |
| CollapsedBorderAndCSSPaddingLogicalHeight() - |
| ScrollbarLogicalHeight(); |
| } |
| return LogicalHeight() - BorderAndPaddingLogicalHeight(); |
| } |
| |
| if (IsFlexItem()) { |
| const LayoutFlexibleBox& flex_box = ToLayoutFlexibleBox(*Parent()); |
| if (flex_box.UseOverrideLogicalHeightForPerentageResolution(*this)) |
| return OverrideContentLogicalHeight(); |
| } |
| |
| 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() && |
| StyleRef().Height().IsAuto() && |
| !(StyleRef().Top().IsAuto() || StyleRef().Bottom().IsAuto())) { |
| LayoutBlock* block = const_cast<LayoutBlock*>(ToLayoutBlock(this)); |
| LogicalExtentComputedValues computed_values; |
| block->ComputeLogicalHeight(block->LogicalHeight(), LayoutUnit(), |
| computed_values); |
| return computed_values.extent_ - block->BorderAndPaddingLogicalHeight() - |
| block->ScrollbarLogicalHeight(); |
| } |
| |
| // FIXME: This is wrong if the containingBlock has a perpendicular writing |
| // mode. |
| LayoutUnit available_height = |
| ContainingBlockLogicalHeightForContent(height_type); |
| // FIXME: This is incorrect if available_height == -1 || 0 |
| 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 (StyleRef().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->LayoutViewport()->ExcludeScrollbars(frame_view->Size())); |
| return LayoutUnit(containing_block->IsHorizontalWritingMode() |
| ? viewport_size.Width() |
| : viewport_size.Height()); |
| } |
| } |
| |
| if (HasOverrideContainingBlockContentLogicalWidth()) |
| return OverrideContainingBlockContentLogicalWidth(); |
| |
| if (containing_block->IsAnonymousBlock() && |
| containing_block->IsRelPositioned()) { |
| // 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. |
| containing_block = ToLayoutBox(containing_block)->Continuation(); |
| // There may be nested parallel inline continuations. We have now found the |
| // innermost inline (which may not be relatively positioned). Locate the |
| // inline that serves as the containing block of this box. |
| while (!containing_block->CanContainOutOfFlowPositionedElement( |
| StyleRef().GetPosition())) { |
| containing_block = ToLayoutBoxModelObject(containing_block->Container()); |
| DCHECK(containing_block->IsLayoutInline()); |
| } |
| } else if (containing_block->IsBox()) { |
| return std::max(LayoutUnit(), |
| ToLayoutBox(containing_block)->ClientLogicalWidth()); |
| } |
| |
| DCHECK(containing_block->IsLayoutInline()); |
| DCHECK(containing_block->CanContainOutOfFlowPositionedElement( |
| StyleRef().GetPosition())); |
| |
| 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->StyleRef().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 (StyleRef().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->LayoutViewport()->ExcludeScrollbars(frame_view->Size())); |
| return containing_block->IsHorizontalWritingMode() |
| ? viewport_size.Height() |
| : viewport_size.Width(); |
| } |
| } |
| |
| if (HasOverrideContainingBlockContentLogicalHeight()) |
| return OverrideContainingBlockContentLogicalHeight(); |
| |
| if (containing_block->IsBox()) |
| return ToLayoutBox(containing_block)->ClientLogicalHeight(); |
| |
| DCHECK(containing_block->IsLayoutInline()); |
| DCHECK(containing_block->CanContainOutOfFlowPositionedElement( |
| StyleRef().GetPosition())); |
| |
| const LayoutInline* flow = ToLayoutInline(containing_block); |
| // If the containing block is empty, return a height of 0. |
| if (flow->IsEmpty()) |
| 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->StyleRef().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->StyleRef().LogicalLeft().IsAuto()) |
| static_position += |
| ValueForLength(curr->StyleRef().LogicalLeft(), |
| curr->ContainingBlock()->AvailableWidth()); |
| else |
| static_position -= |
| ValueForLength(curr->StyleRef().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(); |
| if (container_block->IsBox()) { |
| static_position += |
| ToLayoutBox(container_block)->LogicalLeftScrollbarWidth(); |
| } |
| 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->StyleRef().LogicalLeft().IsAuto()) |
| static_position -= |
| ValueForLength(curr->StyleRef().LogicalLeft(), |
| curr->ContainingBlock()->AvailableWidth()); |
| else |
| static_position += |
| ValueForLength(curr->StyleRef().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->StyleRef().Direction(); |
| |
| bool is_horizontal = IsHorizontalWritingMode(); |
| const LayoutUnit borders_plus_padding = BorderAndPaddingLogicalWidth(); |
| const Length margin_logical_left = |
| is_horizontal ? StyleRef().MarginLeft() : StyleRef().MarginTop(); |
| const Length margin_logical_right = |
| is_horizontal ? StyleRef().MarginRight() : StyleRef().MarginBottom(); |
| |
| Length logical_left_length = StyleRef().LogicalLeft(); |
| Length logical_right_length = StyleRef().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, StyleRef().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 (!StyleRef().LogicalMaxWidth().IsMaxSizeNone()) { |
| LogicalExtentComputedValues max_values; |
| |
| ComputePositionedLogicalWidthUsing( |
| kMaxSize, StyleRef().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 (!StyleRef().LogicalMinWidth().IsZero() || |
| StyleRef().LogicalMinWidth().IsIntrinsic()) { |
| LogicalExtentComputedValues min_values; |
| |
| ComputePositionedLogicalWidthUsing( |
| kMinSize, StyleRef().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) { |
| if (child->IsHorizontalWritingMode()) { |
| if (container_block->HasFlippedBlocksWritingMode()) { |
| // 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 has flipped mode and is perpendicular |
| // to us. |
| logical_left_pos = |
| container_logical_width - logical_width_value - logical_left_pos; |
| logical_left_pos += container_block->BorderRight(); |
| if (container_block->IsBox()) |
| logical_left_pos += ToLayoutBox(container_block)->RightScrollbarWidth(); |
| } else { |
| logical_left_pos += container_block->BorderLeft(); |
| if (container_block->IsBox()) |
| logical_left_pos += ToLayoutBox(container_block)->LeftScrollbarWidth(); |
| } |
| } else { |
| logical_left_pos += 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 = StyleRef().IsLeftToRightDirection() |
| ? computed_values.margins_.start_ |
| : computed_values.margins_.end_; |
| LayoutUnit& margin_logical_right_value = |
| StyleRef().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->StyleRef().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; |
| } |
| } |
| |
| 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); |
| } |
| |
| // Now static_logical_top is relative to container_block's logical top. |
| // Convert it to be relative to containing_block's logical client top. |
| static_logical_top -= container_block->BorderBefore(); |
| if (container_block->IsBox()) { |
| static_logical_top -= |
| ToLayoutBox(container_block)->LogicalTopScrollbarHeight(); |
| } |
| logical_top.SetValue(kFixed, static_logical_top); |
| } |
| |
| 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->StyleRef().IsFlippedBlocksWritingMode() && |
| child->IsHorizontalWritingMode() != |
| container_block->IsHorizontalWritingMode()) || |
| (child->StyleRef().IsFlippedBlocksWritingMode() != |
| container_block->StyleRef().IsFlippedBlocksWritingMode() && |
| child->IsHorizontalWritingMode() == |
| container_block->IsHorizontalWritingMode())) { |
| logical_top_pos = |
| container_logical_height - logical_height_value - logical_top_pos; |
| } |
| |
| // Convert logical_top_pos from container's client space to container's border |
| // box space. |
| if (child->IsHorizontalWritingMode()) { |
| logical_top_pos += container_block->BorderTop(); |
| } else if (container_block->HasFlippedBlocksWritingMode()) { |
| logical_top_pos += container_block->BorderRight(); |
| if (container_block->IsBox()) |
| logical_top_pos += ToLayoutBox(container_block)->RightScrollbarWidth(); |
| } else { |
| logical_top_pos += container_block->BorderLeft(); |
| if (container_block->IsBox()) |
| logical_top_pos += ToLayoutBox(container_block)->LeftScrollbarWidth(); |
| } |
| } |
| |
| 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( |
| const InlineBox* box, |
| int caret_offset, |
| LayoutUnit* extra_width_to_end_of_line) const { |
| // 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() : StyleRef().IsLeftToRightDirection(); |
| |
| if ((!caret_offset) ^ ltr) |
| rect.Move(LayoutSize(Size().Width() - caret_width, LayoutUnit())); |
| |
| if (box) { |
| const 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 = StyleRef().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) const { |
| // 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()) { |
| const Node& node = *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(node)); |
| } |
| return CreatePositionWithAffinity(LastPositionInOrAfterNode(node)); |
| } |
| } |
| |
| // 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->StyleRef().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( |
| NonPseudoNode() ? FirstPositionInOrBeforeNode(*NonPseudoNode()) |
| : Position()); |
| } |
| |
| 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. |
| if (!StyleRef().Width().IsAuto()) |
| return false; |
| |
| // If the containing block is LayoutNG, we will not let legacy layout deal |
| // with positioning of floats or sizing of auto-width new formatting context |
| // block level objects adjacent to them. |
| if (const auto* containing_block = ContainingBlock()) { |
| if (containing_block->IsLayoutNGMixin()) |
| return false; |
| } |
| return true; |
| } |
| |
| 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 = 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 = 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); |
| } |
| |
| // Children of LayoutCustom object's are only considered "items" when it has a |
| // loaded algorithm. |
| bool LayoutBox::IsCustomItem() const { |
| return Parent() && Parent()->IsLayoutCustom() && |
| ToLayoutCustom(Parent())->State() == LayoutCustomState::kBlock; |
| } |
| |
| // LayoutCustom items are only shrink-to-fit during the web-developer defined |
| // layout phase (not during fallback). |
| bool LayoutBox::IsCustomItemShrinkToFit() const { |
| DCHECK(IsCustomItem()); |
| return ToLayoutCustom(Parent())->Phase() == LayoutCustomPhase::kCustom; |
| } |
| |
| void LayoutBox::AddVisualEffectOverflow() { |
| if (!StyleRef().HasVisualOverflowingEffect()) |
| return; |
| |
| // Add in the final overflow with shadows, outsets and outline combined. |
| LayoutRect visual_effect_overflow = BorderBoxRect(); |
| LayoutRectOutsets outsets = ComputeVisualEffectOverflowOutsets(); |
| visual_effect_overflow.Expand(outsets); |
| AddSelfVisualOverflow(visual_effect_overflow); |
| |
| if (overflow_) { |
| overflow_->SetHasSubpixelVisualEffectOutsets( |
| !IsIntegerValue(outsets.Top()) || !IsIntegerValue(outsets.Right()) || |
| !IsIntegerValue(outsets.Bottom()) || !IsIntegerValue(outsets.Left())); |
| } |
| } |
| |
| LayoutRectOutsets LayoutBox::ComputeVisualEffectOverflowOutsets() { |
| const ComputedStyle& style = StyleRef(); |
| DCHECK(style.HasVisualOverflowingEffect()); |
| |
| LayoutRectOutsets outsets = style.BoxDecorationOutsets(); |
| |
| // Box-shadow and border-image-outsets are in physical direction. Flip into |
| // block direction. |
| if (UNLIKELY(HasFlippedBlocksWritingMode())) |
| outsets.FlipHorizontally(); |
| |
| 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); |
| bool outline_affected = rect.Size() != Size(); |
| SetOutlineMayBeAffectedByDescendants(outline_affected); |
| rect.Inflate(style.OutlineOutsetExtent()); |
| outsets.Unite(rect.ToOutsets(Size())); |
| } |
| |
| return outsets; |
| } |
| |
| void LayoutBox::AddVisualOverflowFromChild(const LayoutBox& child, |
| const LayoutSize& delta) { |
| // Never allow flow threads to propagate overflow up to a parent. |
| if (child.IsLayoutFlowThread()) |
| return; |
| |
| // 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); |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBox::AddLayoutOverflowFromChild(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); |
| } |
| |
| void LayoutBox::SetLayoutClientAfterEdge(LayoutUnit client_after_edge) { |
| if (overflow_) |
| overflow_->SetLayoutClientAfterEdge(client_after_edge); |
| } |
| |
| LayoutUnit LayoutBox::LayoutClientAfterEdge() const { |
| return overflow_ ? overflow_->LayoutClientAfterEdge() : ClientLogicalBottom(); |
| } |
| |
| bool LayoutBox::HasTopOverflow() const { |
| return !StyleRef().IsLeftToRightDirection() && !IsHorizontalWritingMode(); |
| } |
| |
| bool LayoutBox::HasLeftOverflow() const { |
| return !StyleRef().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_ = std::make_unique<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_ = |
| std::make_unique<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_ = |
| std::make_unique<BoxOverflowModel>(NoOverflowRect(), border_box); |
| } |
| overflow_->AddContentsVisualOverflow(rect); |
| } |
| |
| void LayoutBox::ClearLayoutOverflow() { |
| if (!overflow_) |
| return; |
| |
| if (!HasSelfVisualOverflow() && ContentsVisualOverflowRect().IsEmpty()) { |
| ClearAllOverflows(); |
| return; |
| } |
| |
| overflow_->SetLayoutOverflow(NoOverflowRect()); |
| } |
| |
| void LayoutBox::ClearVisualOverflow() { |
| if (!overflow_) |
| return; |
| |
| if (!HasLayoutOverflow()) { |
| ClearAllOverflows(); |
| return; |
| } |
| |
| overflow_->ClearContentsVisualOverflow(); |
| overflow_->SetSelfVisualOverflow(BorderBoxRect()); |
| } |
| |
| 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 !StyleRef().LogicalHeight().IsIntrinsicOrAuto() || |
| (!StyleRef().LogicalMaxHeight().IsIntrinsicOrAuto() && |
| !StyleRef().LogicalMaxHeight().IsMaxSizeNone() && |
| (!StyleRef().LogicalMaxHeight().IsPercentOrCalc() || |
| PercentageLogicalHeightIsResolvable())) || |
| (!StyleRef().LogicalMinHeight().IsIntrinsicOrAuto() && |
| StyleRef().LogicalMinHeight().IsPositive() && |
| (!StyleRef().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() && |
| StyleRef().GetPosition() == EPosition::kFixed) || |
| ShouldApplySizeContainment()) |
| 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 |
| LayoutUnit LayoutBox::BaselinePosition( |
| FontBaseline baseline_type, |
| bool /*firstLine*/, |
| LineDirectionMode direction, |
| LinePositionMode line_position_mode) const { |
| DCHECK_EQ(line_position_mode, kPositionOnContainingLine); |
| if (IsAtomicInlineLevel()) { |
| LayoutUnit result = direction == kHorizontalLine |
| ? MarginHeight() + Size().Height() |
| : MarginWidth() + Size().Width(); |
| if (baseline_type == kAlphabeticBaseline) |
| return result; |
| return result - result / 2; |
| } |
| return LayoutUnit(); |
| } |
| |
| PaintLayer* LayoutBox::EnclosingFloatPaintingLayer() const { |
| const LayoutObject* curr = this; |
| while (curr) { |
| PaintLayer* layer = curr->HasLayer() && curr->IsBox() |
| ? ToLayoutBox(curr)->Layer() |
| : nullptr; |
| 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()) { |
| const ComputedStyle* container_style = |
| container ? container->Style() : nullptr; |
| rect.Expand(IsHorizontalWritingMode() |
| ? LayoutSize(LayoutUnit(), MarginAfter(container_style)) |
| : LayoutSize(MarginAfter(container_style), LayoutUnit())); |
| } |
| |
| if (!ShouldClipOverflow() && !ShouldApplyLayoutContainment()) |
| 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 { |
| auto rect = PhysicalPaddingBoxRect(); |
| FlipForWritingMode(rect); |
| return rect; |
| } |
| |
| LayoutRect LayoutBox::VisualOverflowRect() const { |
| if (!overflow_) |
| return BorderBoxRect(); |
| if (HasOverflowClip() || HasMask()) |
| return overflow_->SelfVisualOverflowRect(); |
| return UnionRect(overflow_->SelfVisualOverflowRect(), |
| overflow_->ContentsVisualOverflowRect()); |
| } |
| |
| LayoutPoint LayoutBox::OffsetPoint(const Element* parent) const { |
| return AdjustedPositionRelativeTo(PhysicalLocation(), parent); |
| } |
| |
| LayoutUnit LayoutBox::OffsetLeft(const Element* parent) const { |
| return OffsetPoint(parent).X(); |
| } |
| |
| LayoutUnit LayoutBox::OffsetTop(const Element* parent) const { |
| return OffsetPoint(parent).Y(); |
| } |
| |
| LayoutPoint LayoutBox::FlipForWritingModeForChild( |
| const LayoutBox* child, |
| const LayoutPoint& point) const { |
| if (!StyleRef().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 = 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 StyleRef().LogicalWidth().IsPercentOrCalc() || |
| StyleRef().LogicalMinWidth().IsPercentOrCalc() || |
| StyleRef().LogicalMaxWidth().IsPercentOrCalc(); |
| } |
| |
| bool LayoutBox::HasRelativeLogicalHeight() const { |
| return StyleRef().LogicalHeight().IsPercentOrCalc() || |
| StyleRef().LogicalMinHeight().IsPercentOrCalc() || |
| StyleRef().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( |
| layout_invalidation_reason::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, nullptr, 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); |
| } |
| |
| ShapeOutsideInfo* LayoutBox::GetShapeOutsideInfo() const { |
| return ShapeOutsideInfo::IsEnabledFor(*this) ? ShapeOutsideInfo::Info(*this) |
| : nullptr; |
| } |
| |
| void LayoutBox::ClearPreviousVisualRects() { |
| LayoutBoxModelObject::ClearPreviousVisualRects(); |
| if (PaintLayerScrollableArea* scrollable_area = 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(); |
| |
| LayoutUnit footer_height = |
| View()->GetLayoutState()->HeightOffsetForTableFooters(); |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| LayoutUnit remaining_height; |
| if (!flow_thread) { |
| LayoutUnit page_logical_height = layout_view->PageLogicalHeight(); |
| 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); |
| } |
| } else { |
| remaining_height = flow_thread->PageRemainingLogicalHeightForOffset( |
| offset, page_boundary_rule); |
| } |
| return remaining_height - footer_height; |
| } |
| |
| 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 content_logical_height) const { |
| LayoutUnit strut_to_next_page = |
| PageRemainingLogicalHeightForOffset(offset, kAssociateWithLatterPage); |
| |
| LayoutState* layout_state = View()->GetLayoutState(); |
| strut_to_next_page += layout_state->HeightOffsetForTableFooters(); |
| // 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()) { |
| 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* const 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); |
| } |
| } |
| |
| bool LayoutBox::AllowedToPropagateRecursiveScrollToParentFrame( |
| const WebScrollIntoViewParams& params) { |
| if (!GetFrameView()->SafeToPropagateScrollToParent()) |
| return false; |
| |
| if (params.GetScrollType() != kProgrammaticScroll) |
| return true; |
| |
| return !GetDocument().IsVerticalScrollEnforced(); |
| } |
| |
| SnapAreaSet* LayoutBox::SnapAreas() const { |
| return rare_data_ ? rare_data_->snap_areas_.get() : nullptr; |
| } |
| |
| CustomLayoutChild* LayoutBox::GetCustomLayoutChild() const { |
| DCHECK(rare_data_); |
| DCHECK(rare_data_->layout_child_); |
| return rare_data_->layout_child_.Get(); |
| } |
| |
| void LayoutBox::AddCustomLayoutChildIfNeeded() { |
| if (!IsCustomItem()) |
| return; |
| |
| const AtomicString& name = Parent()->StyleRef().DisplayLayoutCustomName(); |
| LayoutWorklet* worklet = LayoutWorklet::From(*GetDocument().domWindow()); |
| const CSSLayoutDefinition* definition = |
| worklet->Proxy()->FindDefinition(name); |
| |
| // If there isn't a definition yet, the web developer defined layout isn't |
| // loaded yet (or is invalid). The layout tree will get re-attached when |
| // loaded, so don't bother creating a script representation of this node yet. |
| if (!definition) |
| return; |
| |
| EnsureRareData().layout_child_ = |
| MakeGarbageCollected<CustomLayoutChild>(*definition, this); |
| } |
| |
| void LayoutBox::ClearCustomLayoutChild() { |
| if (!rare_data_) |
| return; |
| |
| if (rare_data_->layout_child_) |
| rare_data_->layout_child_->ClearLayoutBox(); |
| |
| rare_data_->layout_child_ = nullptr; |
| } |
| |
| LayoutRect LayoutBox::DebugRect() const { |
| LayoutRect rect = FrameRect(); |
| |
| LayoutBlock* block = ContainingBlock(); |
| if (block) |
| block->AdjustChildDebugRect(rect); |
| |
| return rect; |
| } |
| |
| bool LayoutBox::ComputeShouldClipOverflow() const { |
| return HasOverflowClip() || ShouldApplyPaintContainment() || HasControlClip(); |
| } |
| |
| void LayoutBox::MutableForPainting:: |
| SavePreviousContentBoxRectAndLayoutOverflowRect() { |
| auto& rare_data = GetLayoutBox().EnsureRareData(); |
| rare_data.has_previous_content_box_rect_and_layout_overflow_rect_ = true; |
| rare_data.previous_physical_content_box_rect_ = |
| GetLayoutBox().PhysicalContentBoxRect(); |
| rare_data.previous_physical_layout_overflow_rect_ = |
| GetLayoutBox().PhysicalLayoutOverflowRect(); |
| } |
| |
| float LayoutBox::VisualRectOutsetForRasterEffects() const { |
| // If the box has subpixel visual effect outsets, as the visual effect may be |
| // painted along the pixel-snapped border box, the pixels on the anti-aliased |
| // edge of the effect may overflow the calculated visual rect. Expand visual |
| // rect by one pixel in the case. |
| return overflow_ && overflow_->HasSubpixelVisualEffectOutsets() ? 1 : 0; |
| } |
| |
| TextDirection LayoutBox::ResolvedDirection() const { |
| if (IsInline() && IsAtomicInlineLevel()) { |
| const auto fragments = NGPaintFragment::InlineFragmentsFor(this); |
| if (fragments.IsInLayoutNGInlineFormattingContext()) { |
| DCHECK(*fragments.begin()) << this; |
| const NGPaintFragment* fragment = *fragments.begin(); |
| return fragment->PhysicalFragment().ResolvedDirection(); |
| } |
| |
| if (InlineBoxWrapper()) |
| return InlineBoxWrapper()->Direction(); |
| } |
| return StyleRef().Direction(); |
| } |
| |
| } // namespace blink |