| /* |
| * 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 Apple Inc. All rights reserved. |
| * Copyright (C) 2010 Google Inc. 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_model_object.h" |
| |
| #include "cc/input/main_thread_scrolling_reason.h" |
| #include "third_party/blink/renderer/core/dom/node_computed_style.h" |
| #include "third_party/blink/renderer/core/editing/ime/input_method_controller.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.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/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/html/html_body_element.h" |
| #include "third_party/blink/renderer/core/layout/constraint_space.h" |
| #include "third_party/blink/renderer/core/layout/geometry/transform_state.h" |
| #include "third_party/blink/renderer/core/layout/inline/inline_cursor.h" |
| #include "third_party/blink/renderer/core/layout/layout_block.h" |
| #include "third_party/blink/renderer/core/layout/layout_flow_thread.h" |
| #include "third_party/blink/renderer/core/layout/layout_inline.h" |
| #include "third_party/blink/renderer/core/layout/layout_object_inlines.h" |
| #include "third_party/blink/renderer/core/layout/layout_result.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/legacy_layout_tree_walking.h" |
| #include "third_party/blink/renderer/core/layout/table/layout_table_section.h" |
| #include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h" |
| #include "third_party/blink/renderer/core/paint/inline_paint_context.h" |
| #include "third_party/blink/renderer/core/paint/object_paint_invalidator.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/length_functions.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| void MarkBoxForRelayoutAfterSplit(LayoutBoxModelObject* box) { |
| box->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( |
| layout_invalidation_reason::kAnonymousBlockChange); |
| } |
| |
| void CollapseLoneAnonymousBlockChild(LayoutBox* parent, LayoutObject* child) { |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(child); |
| auto* parent_block_flow = DynamicTo<LayoutBlockFlow>(parent); |
| if (!child->IsAnonymousBlock() || !child_block_flow) |
| return; |
| if (!parent_block_flow) |
| return; |
| parent_block_flow->CollapseAnonymousBlockChild(child_block_flow); |
| } |
| |
| bool NeedsAnchorPositionScrollData(Element& element, |
| const ComputedStyle& style) { |
| // `AnchorPositionScrollData` is for anchor positioned elements, which must be |
| // absolutely positioned. |
| if (!style.HasOutOfFlowPosition()) { |
| return false; |
| } |
| // There's an explicitly set default anchor. |
| if (style.PositionAnchor()) { |
| return true; |
| } |
| // Now we have `position-anchor: implicit`. We need `AnchorPositionScrollData` |
| // only if there's an implicit anchor element to track. |
| return element.ImplicitAnchorElement(); |
| } |
| |
| } // namespace |
| |
| LayoutBoxModelObject::LayoutBoxModelObject(ContainerNode* node) |
| : LayoutObject(node) {} |
| |
| LayoutBoxModelObject::~LayoutBoxModelObject() = default; |
| |
| void LayoutBoxModelObject::WillBeDestroyed() { |
| NOT_DESTROYED(); |
| |
| if (!DocumentBeingDestroyed()) { |
| GetDocument() |
| .GetFrame() |
| ->GetInputMethodController() |
| .LayoutObjectWillBeDestroyed(*this); |
| } |
| |
| LayoutObject::WillBeDestroyed(); |
| |
| if (HasLayer()) |
| DestroyLayer(); |
| |
| if (GetFrameView()) { |
| SetIsBackgroundAttachmentFixedObject(false); |
| } |
| |
| // Our layer should have been destroyed and cleared by now |
| DCHECK(!HasLayer()); |
| DCHECK(!Layer()); |
| } |
| |
| void LayoutBoxModelObject::StyleWillChange(StyleDifference diff, |
| const ComputedStyle& new_style) { |
| NOT_DESTROYED(); |
| // Change of stacked/stacking context status may cause change of this or |
| // descendant PaintLayer's CompositingContainer, so we need to eagerly |
| // invalidate the current compositing container chain which may have painted |
| // cached subsequences containing this object or descendant objects. |
| if (Style() && |
| (IsStacked() != IsStacked(new_style) || |
| IsStackingContext() != IsStackingContext(new_style)) && |
| // ObjectPaintInvalidator requires this. |
| IsRooted()) { |
| ObjectPaintInvalidator(*this).SlowSetPaintingLayerNeedsRepaint(); |
| } |
| |
| if (Style()) { |
| LayoutFlowThread* flow_thread = FlowThreadContainingBlock(); |
| if (flow_thread && flow_thread != this) { |
| flow_thread->FlowThreadDescendantStyleWillChange(this, diff, new_style); |
| } |
| } |
| |
| LayoutObject::StyleWillChange(diff, new_style); |
| } |
| |
| DISABLE_CFI_PERF |
| void LayoutBoxModelObject::StyleDidChange(StyleDifference diff, |
| const ComputedStyle* old_style) { |
| NOT_DESTROYED(); |
| bool had_transform_related_property = HasTransformRelatedProperty(); |
| bool had_filter_inducing_property = HasFilterInducingProperty(); |
| bool had_non_initial_backdrop_filter = HasNonInitialBackdropFilter(); |
| bool had_layer = HasLayer(); |
| bool layer_was_self_painting = had_layer && Layer()->IsSelfPaintingLayer(); |
| bool could_contain_fixed = CanContainFixedPositionObjects(); |
| bool could_contain_absolute = CanContainAbsolutePositionObjects(); |
| |
| LayoutObject::StyleDidChange(diff, old_style); |
| UpdateFromStyle(); |
| |
| // When an out-of-flow-positioned element changes its display between block |
| // and inline-block, then an incremental layout on the element's containing |
| // block lays out the element through LayoutPositionedObjects, which skips |
| // laying out the element's parent. |
| // The element's parent needs to relayout so that it calls LayoutBlockFlow:: |
| // setStaticInlinePositionForChild with the out-of-flow-positioned child, so |
| // that when it's laid out, its LayoutBox::computePositionedLogicalWidth/ |
| // Height takes into account its new inline/block position rather than its old |
| // block/inline position. |
| // Position changes and other types of display changes are handled elsewhere. |
| if (old_style && IsOutOfFlowPositioned() && Parent() && |
| (StyleRef().GetPosition() == old_style->GetPosition()) && |
| (StyleRef().IsOriginalDisplayInlineType() != |
| old_style->IsOriginalDisplayInlineType())) |
| Parent()->SetNeedsLayout(layout_invalidation_reason::kChildChanged, |
| kMarkContainerChain); |
| |
| // Clear our sticky constraints if we are no longer sticky. |
| if (Layer() && old_style->HasStickyConstrainedPosition() && |
| !StyleRef().HasStickyConstrainedPosition()) { |
| SetStickyConstraints(nullptr); |
| } |
| |
| PaintLayerType type = LayerTypeRequired(); |
| if (type != kNoPaintLayer) { |
| if (!Layer()) { |
| // In order to update this object properly, we need to lay it out again. |
| // However, if we have never laid it out, don't mark it for layout. If |
| // this is a new object, it may not yet have been inserted into the tree, |
| // and if we mark it for layout then, we risk upsetting the tree |
| // insertion machinery. |
| if (EverHadLayout()) |
| SetChildNeedsLayout(); |
| |
| CreateLayerAfterStyleChange(); |
| } |
| } else if (Layer() && Layer()->Parent()) { |
| Layer()->UpdateFilters(old_style, StyleRef()); |
| Layer()->UpdateBackdropFilters(old_style, StyleRef()); |
| Layer()->UpdateClipPath(old_style, StyleRef()); |
| Layer()->UpdateOffsetPath(old_style, StyleRef()); |
| // Calls DestroyLayer() which clears the layer. |
| Layer()->RemoveOnlyThisLayerAfterStyleChange(old_style); |
| if (EverHadLayout()) |
| SetChildNeedsLayout(); |
| if (had_transform_related_property || had_filter_inducing_property || |
| had_non_initial_backdrop_filter) { |
| SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation( |
| layout_invalidation_reason::kStyleChange); |
| } |
| } |
| |
| bool can_contain_fixed = CanContainFixedPositionObjects(); |
| bool can_contain_absolute = CanContainAbsolutePositionObjects(); |
| |
| if (old_style && (could_contain_fixed != can_contain_fixed || |
| could_contain_absolute != can_contain_absolute)) { |
| // If out of flow element containment changed, then we need to force a |
| // subtree paint property update, since the children elements may now be |
| // referencing a different container. |
| AddSubtreePaintPropertyUpdateReason( |
| SubtreePaintPropertyUpdateReason::kContainerChainMayChange); |
| } else if (had_layer == HasLayer() && |
| (had_transform_related_property != HasTransformRelatedProperty() || |
| had_filter_inducing_property != HasFilterInducingProperty() || |
| had_non_initial_backdrop_filter != |
| HasNonInitialBackdropFilter())) { |
| // This affects whether to create transform, filter, or effect nodes. Note |
| // that if the HasLayer() value changed, then all of this was already set in |
| // CreateLayerAfterStyleChange() or DestroyLayer(). |
| SetNeedsPaintPropertyUpdate(); |
| } |
| |
| if (old_style && Parent()) { |
| if (LayoutFlowThread* flow_thread = FlowThreadContainingBlock()) { |
| if (flow_thread != this) { |
| flow_thread->FlowThreadDescendantStyleDidChange(this, diff, *old_style); |
| } |
| } |
| |
| LayoutBlock* block = InclusiveContainingBlock(); |
| |
| if ((could_contain_fixed && !can_contain_fixed) || |
| (could_contain_absolute && !can_contain_absolute)) { |
| // Clear our positioned objects list. Our absolute and fixed positioned |
| // descendants will be inserted into our containing block's positioned |
| // objects list during layout. |
| block->RemovePositionedObjects(nullptr); |
| } |
| if (!could_contain_absolute && can_contain_absolute) { |
| // Remove our absolute positioned descendants from their current |
| // containing block. |
| // They will be inserted into our positioned objects list during layout. |
| if (LayoutBlock* cb = block->ContainingBlockForAbsolutePosition()) |
| cb->RemovePositionedObjects(this); |
| } |
| if (!could_contain_fixed && can_contain_fixed) { |
| // Remove our fixed positioned descendants from their current containing |
| // block. |
| // They will be inserted into our positioned objects list during layout. |
| if (LayoutBlock* cb = block->ContainingBlockForFixedPosition()) |
| cb->RemovePositionedObjects(this); |
| } |
| } |
| |
| if (Layer()) { |
| // The previous CompositingContainer chain was marked for repaint via |
| // |LayoutBoxModelObject::StyleWillChange| but changes to stacking can |
| // change the compositing container so we need to ensure the new |
| // CompositingContainer is also marked for repaint. |
| if (old_style && |
| (IsStacked() != IsStacked(*old_style) || |
| IsStackingContext() != IsStackingContext(*old_style)) && |
| // ObjectPaintInvalidator requires this. |
| IsRooted()) { |
| ObjectPaintInvalidator(*this).SlowSetPaintingLayerNeedsRepaint(); |
| } |
| |
| Layer()->StyleDidChange(diff, old_style); |
| if (had_layer && Layer()->IsSelfPaintingLayer() != layer_was_self_painting) |
| SetChildNeedsLayout(); |
| } |
| |
| // The used style for body background may change due to computed style change |
| // on the document element because of change of BackgroundTransfersToView() |
| // which depends on the document element style. |
| if (IsDocumentElement()) { |
| if (const HTMLBodyElement* body = GetDocument().FirstBodyElement()) { |
| if (auto* body_object = |
| DynamicTo<LayoutBoxModelObject>(body->GetLayoutObject())) { |
| bool new_body_background_transfers = |
| body_object->BackgroundTransfersToView(Style()); |
| bool old_body_background_transfers = |
| old_style && body_object->BackgroundTransfersToView(old_style); |
| if (new_body_background_transfers != old_body_background_transfers && |
| body_object->Style() && body_object->StyleRef().HasBackground()) { |
| body_object->SetBackgroundNeedsFullPaintInvalidation(); |
| } |
| } |
| } |
| } |
| |
| if (old_style && |
| old_style->BackfaceVisibility() != StyleRef().BackfaceVisibility()) { |
| SetNeedsPaintPropertyUpdate(); |
| } |
| |
| // We can't squash across a layout containment boundary. So, if the |
| // containment changes, we need to update the compositing inputs. |
| if (old_style && |
| ShouldApplyLayoutContainment(*old_style) != |
| ShouldApplyLayoutContainment() && |
| Layer()) { |
| Layer()->SetNeedsCompositingInputsUpdate(); |
| } |
| |
| if (Element* element = DynamicTo<Element>(GetNode())) { |
| if (NeedsAnchorPositionScrollData(*element, StyleRef())) { |
| element->EnsureAnchorPositionScrollData(); |
| } else { |
| element->RemoveAnchorPositionScrollData(); |
| } |
| } |
| } |
| |
| void LayoutBoxModelObject::CreateLayerAfterStyleChange() { |
| NOT_DESTROYED(); |
| DCHECK(!HasLayer() && !Layer()); |
| FragmentData& first_fragment = GetMutableForPainting().FirstFragment(); |
| first_fragment.EnsureId(); |
| first_fragment.SetLayer(MakeGarbageCollected<PaintLayer>(this)); |
| SetHasLayer(true); |
| Layer()->InsertOnlyThisLayerAfterStyleChange(); |
| // Creating a layer may affect existence of the LocalBorderBoxProperties, so |
| // we need to ensure that we update paint properties. |
| SetNeedsPaintPropertyUpdate(); |
| } |
| |
| void LayoutBoxModelObject::DestroyLayer() { |
| NOT_DESTROYED(); |
| DCHECK(HasLayer() && Layer()); |
| SetHasLayer(false); |
| GetMutableForPainting().FirstFragment().SetLayer(nullptr); |
| // Removing a layer may affect existence of the LocalBorderBoxProperties, so |
| // we need to ensure that we update paint properties. |
| SetNeedsPaintPropertyUpdate(); |
| } |
| |
| bool LayoutBoxModelObject::HasSelfPaintingLayer() const { |
| NOT_DESTROYED(); |
| return Layer() && Layer()->IsSelfPaintingLayer(); |
| } |
| |
| PaintLayerScrollableArea* LayoutBoxModelObject::GetScrollableArea() const { |
| NOT_DESTROYED(); |
| return Layer() ? Layer()->GetScrollableArea() : nullptr; |
| } |
| |
| void LayoutBoxModelObject::AddOutlineRectsForNormalChildren( |
| OutlineRectCollector& collector, |
| const PhysicalOffset& additional_offset, |
| OutlineType include_block_overflows) const { |
| NOT_DESTROYED(); |
| for (LayoutObject* child = SlowFirstChild(); child; |
| child = child->NextSibling()) { |
| // Outlines of out-of-flow positioned descendants are handled in |
| // LayoutBlock::AddOutlineRects(). |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| AddOutlineRectsForDescendant(*child, collector, additional_offset, |
| include_block_overflows); |
| } |
| } |
| |
| void LayoutBoxModelObject::AddOutlineRectsForDescendant( |
| const LayoutObject& descendant, |
| OutlineRectCollector& collector, |
| const PhysicalOffset& additional_offset, |
| OutlineType include_block_overflows) const { |
| NOT_DESTROYED(); |
| if (descendant.IsText()) { |
| return; |
| } |
| |
| if (descendant.HasLayer()) { |
| OutlineRectCollector* descendant_collector = |
| collector.ForDescendantCollector(); |
| descendant.AddOutlineRects(*descendant_collector, nullptr, PhysicalOffset(), |
| include_block_overflows); |
| collector.Combine(descendant_collector, descendant, this, |
| additional_offset); |
| return; |
| } |
| |
| if (descendant.IsBox()) { |
| descendant.AddOutlineRects( |
| collector, nullptr, |
| additional_offset + To<LayoutBox>(descendant).PhysicalLocation(), |
| include_block_overflows); |
| return; |
| } |
| |
| if (descendant.IsLayoutInline()) { |
| // As an optimization, an ancestor has added rects for its line boxes |
| // covering descendants' line boxes, so descendants don't need to add line |
| // boxes again. For example, if the parent is a LayoutBlock, it adds rects |
| // for its RootOutlineBoxes which cover the line boxes of this LayoutInline. |
| // So the LayoutInline needs to add rects for children and continuations |
| // only. |
| To<LayoutInline>(descendant) |
| .AddOutlineRectsForNormalChildren(collector, additional_offset, |
| include_block_overflows); |
| return; |
| } |
| |
| descendant.AddOutlineRects(collector, nullptr, additional_offset, |
| include_block_overflows); |
| } |
| |
| void LayoutBoxModelObject::RecalcVisualOverflow() { |
| // |PaintLayer| calls this function when |HasSelfPaintingLayer|. When |this| |
| // is an inline box or an atomic inline, its ink overflow is stored in |
| // |FragmentItem| in the inline formatting context. |
| if (IsInline() && IsInLayoutNGInlineFormattingContext()) { |
| DCHECK(HasSelfPaintingLayer()); |
| InlineCursor cursor; |
| InlinePaintContext inline_context; |
| for (cursor.MoveTo(*this); cursor; cursor.MoveToNextForSameLayoutObject()) { |
| InlinePaintContext::ScopedInlineBoxAncestors scoped_items( |
| cursor, &inline_context); |
| cursor.Current().RecalcInkOverflow(cursor, &inline_context); |
| } |
| return; |
| } |
| |
| LayoutObject::RecalcVisualOverflow(); |
| } |
| |
| bool LayoutBoxModelObject::ShouldBeHandledAsInline( |
| const ComputedStyle& style) const { |
| if (style.IsDisplayInlineType()) { |
| return true; |
| } |
| // Table-internal display types create anonymous inline or block <table>s |
| // depending on the parent. But if an element with a table-internal display |
| // type creates a domain-specific LayoutObject such as LayoutImage, such |
| // anonymous <table> is not created, and the LayoutObject should adjust |
| // IsInline flag for inlinifying. |
| // |
| // LayoutRubyBase and LayoutRubyText should be blocks even in a ruby. |
| return style.IsInInlinifyingDisplay() && !IsTablePart() && !IsRubyBase() && |
| !IsRubyText(); |
| } |
| |
| void LayoutBoxModelObject::UpdateFromStyle() { |
| NOT_DESTROYED(); |
| const ComputedStyle& style_to_use = StyleRef(); |
| SetHasBoxDecorationBackground(style_to_use.HasBoxDecorationBackground()); |
| SetInline(ShouldBeHandledAsInline(style_to_use)); |
| SetPositionState(style_to_use.GetPosition()); |
| SetHorizontalWritingMode(style_to_use.IsHorizontalWritingMode()); |
| SetCanContainAbsolutePositionObjects( |
| ComputeIsAbsoluteContainer(&style_to_use)); |
| SetCanContainFixedPositionObjects(ComputeIsFixedContainer(&style_to_use)); |
| SetIsBackgroundAttachmentFixedObject( |
| !BackgroundTransfersToView() && |
| StyleRef().HasFixedAttachmentBackgroundImage()); |
| } |
| |
| void LayoutBoxModelObject::UpdateCanCompositeBackgroundAttachmentFixed( |
| bool enable_composited_background_attachment_fixed) { |
| SetCanCompositeBackgroundAttachmentFixed( |
| enable_composited_background_attachment_fixed && |
| ComputeCanCompositeBackgroundAttachmentFixed()); |
| } |
| |
| PhysicalRect LayoutBoxModelObject::VisualOverflowRectIncludingFilters() const { |
| NOT_DESTROYED(); |
| return ApplyFiltersToRect(VisualOverflowRect()); |
| } |
| |
| PhysicalRect LayoutBoxModelObject::ApplyFiltersToRect( |
| const PhysicalRect& rect) const { |
| NOT_DESTROYED(); |
| if (!StyleRef().HasFilter()) { |
| return rect; |
| } |
| gfx::RectF float_rect(rect); |
| gfx::RectF filter_reference_box = Layer()->FilterReferenceBox(); |
| if (!filter_reference_box.size().IsZero()) { |
| float_rect.UnionEvenIfEmpty(filter_reference_box); |
| } |
| float_rect = Layer()->MapRectForFilter(float_rect); |
| return PhysicalRect::EnclosingRect(float_rect); |
| } |
| |
| LayoutBlock* LayoutBoxModelObject::StickyContainer() const { |
| return ContainingBlock(); |
| } |
| |
| StickyPositionScrollingConstraints* |
| LayoutBoxModelObject::ComputeStickyPositionConstraints() const { |
| NOT_DESTROYED(); |
| DCHECK(StyleRef().HasStickyConstrainedPosition()); |
| |
| StickyPositionScrollingConstraints* constraints = |
| MakeGarbageCollected<StickyPositionScrollingConstraints>(); |
| |
| bool is_fixed_to_view = false; |
| const auto* scroll_container_layer = |
| Layer()->ContainingScrollContainerLayer(&is_fixed_to_view); |
| constraints->containing_scroll_container_layer = scroll_container_layer; |
| constraints->is_fixed_to_view = is_fixed_to_view; |
| |
| // Skip anonymous containing blocks except for anonymous fieldset content box. |
| LayoutBlock* sticky_container = StickyContainer(); |
| while (sticky_container->IsAnonymous()) { |
| if (sticky_container->Parent() && |
| sticky_container->Parent()->IsFieldset()) { |
| break; |
| } |
| sticky_container = sticky_container->ContainingBlock(); |
| } |
| |
| const auto* scroll_container = scroll_container_layer->GetLayoutBox(); |
| DCHECK(scroll_container); |
| const PhysicalOffset scroll_container_border_offset( |
| scroll_container->BorderLeft(), scroll_container->BorderTop()); |
| |
| MapCoordinatesFlags flags = |
| kIgnoreTransforms | kIgnoreScrollOffset | kIgnoreStickyOffset; |
| |
| // Compute the sticky-container rect. |
| { |
| PhysicalRect scroll_container_relative_containing_block_rect; |
| if (sticky_container == scroll_container) { |
| scroll_container_relative_containing_block_rect = |
| sticky_container->ScrollableOverflowRect(); |
| } else { |
| PhysicalRect local_rect = sticky_container->PhysicalPaddingBoxRect(); |
| scroll_container_relative_containing_block_rect = |
| sticky_container->LocalToAncestorRect(local_rect, scroll_container, |
| flags); |
| } |
| |
| // Make relative to the padding-box instead of border-box. |
| scroll_container_relative_containing_block_rect.Move( |
| -scroll_container_border_offset); |
| |
| // This is removing the padding of the containing block's overflow rect to |
| // get the flow box rectangle and removing the margin of the sticky element |
| // to ensure that space between the sticky element and its containing flow |
| // box. It is an open issue whether the margin should collapse. See |
| // https://www.w3.org/TR/css-position-3/#sticky-pos |
| scroll_container_relative_containing_block_rect.Contract( |
| sticky_container->PaddingOutsets()); |
| if (!RuntimeEnabledFeatures::LayoutIgnoreMarginsForStickyEnabled()) { |
| // Sticky positioned element ignore any override logical width on the |
| // containing block, as they don't call |
| // containingBlockLogicalWidthForContent. |
| // It's unclear whether this is totally fine. |
| // Compute the container-relative area within which the sticky element is |
| // allowed to move. |
| LayoutUnit max_width = sticky_container->AvailableLogicalWidth(); |
| scroll_container_relative_containing_block_rect.ContractEdges( |
| MinimumValueForLength(StyleRef().MarginTop(), max_width), |
| MinimumValueForLength(StyleRef().MarginRight(), max_width), |
| MinimumValueForLength(StyleRef().MarginBottom(), max_width), |
| MinimumValueForLength(StyleRef().MarginLeft(), max_width)); |
| } |
| |
| constraints->scroll_container_relative_containing_block_rect = |
| scroll_container_relative_containing_block_rect; |
| } |
| |
| // The location container for boxes is not always the containing block. |
| LayoutObject* location_container = |
| IsLayoutInline() ? Container() : To<LayoutBox>(this)->LocationContainer(); |
| |
| // Compute the sticky-box rect. |
| PhysicalRect sticky_box_rect; |
| { |
| if (IsLayoutInline()) { |
| sticky_box_rect = To<LayoutInline>(this)->PhysicalLinesBoundingBox(); |
| } else { |
| const LayoutBox& box = To<LayoutBox>(*this); |
| sticky_box_rect = PhysicalRect(box.PhysicalLocation(), box.Size()); |
| } |
| |
| PhysicalRect scroll_container_relative_sticky_box_rect = |
| location_container->LocalToAncestorRect(sticky_box_rect, |
| scroll_container, flags); |
| |
| // Make relative to the padding-box instead of border-box. |
| scroll_container_relative_sticky_box_rect.Move( |
| -scroll_container_border_offset); |
| constraints->scroll_container_relative_sticky_box_rect = |
| scroll_container_relative_sticky_box_rect; |
| } |
| |
| // To correctly compute the offsets, the constraints need to know about any |
| // nested sticky elements between themselves and their sticky-container, |
| // and between the sticky-container and their scroll-container. |
| // |
| // The respective search ranges are [location_container, sticky_container) |
| // and [sticky_container, scroll_container). |
| constraints->nearest_sticky_layer_shifting_sticky_box = |
| location_container->FindFirstStickyContainer(sticky_container); |
| constraints->nearest_sticky_layer_shifting_containing_block = |
| sticky_container->FindFirstStickyContainer(scroll_container); |
| |
| constraints->constraining_rect = |
| scroll_container->ComputeStickyConstrainingRect(); |
| |
| // Compute the insets. |
| { |
| auto ResolveInset = [](const Length& length, |
| LayoutUnit size) -> std::optional<LayoutUnit> { |
| if (length.IsAuto()) { |
| return std::nullopt; |
| } |
| return MinimumValueForLength(length, size); |
| }; |
| |
| const PhysicalSize available_size = constraints->constraining_rect.size; |
| const auto& style = StyleRef(); |
| std::optional<LayoutUnit> left = |
| ResolveInset(style.Left(), available_size.width); |
| std::optional<LayoutUnit> right = |
| ResolveInset(style.Right(), available_size.width); |
| std::optional<LayoutUnit> top = |
| ResolveInset(style.Top(), available_size.height); |
| std::optional<LayoutUnit> bottom = |
| ResolveInset(style.Bottom(), available_size.height); |
| |
| // Skip the end inset if there is not enough space to honor both insets. |
| if (left && right) { |
| if (*left + *right + sticky_box_rect.Width() > available_size.width) { |
| if (style.IsLeftToRightDirection()) { |
| right = std::nullopt; |
| } else { |
| left = std::nullopt; |
| } |
| } |
| } |
| if (top && bottom) { |
| // TODO(flackr): Exclude top or bottom edge offset depending on the |
| // writing mode when related sections are fixed in spec. See |
| // http://lists.w3.org/Archives/Public/www-style/2014May/0286.html |
| if (*top + *bottom + sticky_box_rect.Height() > available_size.height) { |
| bottom = std::nullopt; |
| } |
| } |
| |
| constraints->left_inset = left; |
| constraints->right_inset = right; |
| constraints->top_inset = top; |
| constraints->bottom_inset = bottom; |
| } |
| |
| return constraints; |
| } |
| |
| PhysicalOffset LayoutBoxModelObject::StickyPositionOffset() const { |
| NOT_DESTROYED(); |
| // TODO(chrishtr): StickyPositionOffset depends data updated after layout at |
| // present, but there are callsites within Layout for it. |
| auto* constraints = StickyConstraints(); |
| return constraints ? constraints->StickyOffset() : PhysicalOffset(); |
| } |
| |
| PhysicalOffset LayoutBoxModelObject::AdjustedPositionRelativeTo( |
| const PhysicalOffset& start_point, |
| const Element* offset_parent) const { |
| NOT_DESTROYED(); |
| // If the element is the HTML body element or doesn't have a parent |
| // return 0 and stop this algorithm. |
| if (IsBody() || !Parent()) |
| return PhysicalOffset(); |
| |
| PhysicalOffset reference_point = start_point; |
| |
| // If the offsetParent is null, return the distance between the canvas origin |
| // and the left/top border edge of the element and stop this algorithm. |
| if (!offset_parent) |
| return reference_point; |
| |
| if (const LayoutBoxModelObject* offset_parent_object = |
| offset_parent->GetLayoutBoxModelObject()) { |
| if (!IsOutOfFlowPositioned()) { |
| if (IsStickyPositioned()) { |
| reference_point += StickyPositionOffset(); |
| } |
| |
| // Note that we may fail to find |offsetParent| while walking the |
| // container chain, if |offsetParent| is an inline split into |
| // continuations: <body style="display:inline;" id="offsetParent"> |
| // <div id="this"> |
| // This is why we have to do a nullptr check here. |
| for (const LayoutObject* current = Container(); |
| current && current->GetNode() != offset_parent; |
| current = current->Container()) { |
| // FIXME: What are we supposed to do inside SVG content? |
| reference_point += current->ColumnOffset(reference_point); |
| if (current->IsBox()) { |
| reference_point += To<LayoutBox>(current)->PhysicalLocation(); |
| } |
| } |
| |
| if (offset_parent_object->IsBox() && offset_parent_object->IsBody() && |
| !offset_parent_object->IsPositioned()) { |
| reference_point += |
| To<LayoutBox>(offset_parent_object)->PhysicalLocation(); |
| } |
| } else if (UNLIKELY(IsBox() && |
| To<LayoutBox>(this) |
| ->NeedsAnchorPositionScrollAdjustment())) { |
| reference_point += |
| To<LayoutBox>(this)->AnchorPositionScrollTranslationOffset(); |
| } |
| |
| if (offset_parent_object->IsLayoutInline()) { |
| const auto* inline_parent = To<LayoutInline>(offset_parent_object); |
| reference_point -= inline_parent->FirstLineBoxTopLeft(); |
| } |
| |
| if (offset_parent_object->IsBox() && !offset_parent_object->IsBody()) { |
| auto* box = To<LayoutBox>(offset_parent_object); |
| reference_point -= PhysicalOffset(box->BorderLeft(), box->BorderTop()); |
| } |
| } |
| |
| return reference_point; |
| } |
| |
| LayoutUnit LayoutBoxModelObject::OffsetLeft(const Element* parent) const { |
| NOT_DESTROYED(); |
| // Note that LayoutInline and LayoutBox override this to pass a different |
| // startPoint to adjustedPositionRelativeTo. |
| return AdjustedPositionRelativeTo(PhysicalOffset(), parent).left; |
| } |
| |
| LayoutUnit LayoutBoxModelObject::OffsetTop(const Element* parent) const { |
| NOT_DESTROYED(); |
| // Note that LayoutInline and LayoutBox override this to pass a different |
| // startPoint to adjustedPositionRelativeTo. |
| return AdjustedPositionRelativeTo(PhysicalOffset(), parent).top; |
| } |
| |
| LayoutUnit LayoutBoxModelObject::ComputedCSSPadding( |
| const Length& padding) const { |
| NOT_DESTROYED(); |
| LayoutUnit w; |
| if (padding.IsPercentOrCalc()) |
| w = ContainingBlockLogicalWidthForContent(); |
| return MinimumValueForLength(padding, w); |
| } |
| |
| LayoutUnit LayoutBoxModelObject::ContainingBlockLogicalWidthForContent() const { |
| NOT_DESTROYED(); |
| return ContainingBlock()->AvailableLogicalWidth(); |
| } |
| |
| LogicalRect LayoutBoxModelObject::LocalCaretRectForEmptyElement( |
| LayoutUnit width, |
| LayoutUnit text_indent_offset) const { |
| NOT_DESTROYED(); |
| DCHECK(!SlowFirstChild() || SlowFirstChild()->IsPseudoElement()); |
| |
| // FIXME: This does not take into account either :first-line or :first-letter |
| // However, as soon as some content is entered, the line boxes will be |
| // constructed and this kludge is not called any more. So only the caret size |
| // of an empty :first-line'd block is wrong. I think we can live with that. |
| const ComputedStyle& current_style = FirstLineStyleRef(); |
| |
| enum CaretAlignment { kAlignLeft, kAlignRight, kAlignCenter }; |
| |
| CaretAlignment alignment = kAlignLeft; |
| |
| switch (current_style.GetTextAlign()) { |
| case ETextAlign::kLeft: |
| case ETextAlign::kWebkitLeft: |
| break; |
| case ETextAlign::kCenter: |
| case ETextAlign::kWebkitCenter: |
| alignment = kAlignCenter; |
| break; |
| case ETextAlign::kRight: |
| case ETextAlign::kWebkitRight: |
| alignment = kAlignRight; |
| break; |
| case ETextAlign::kJustify: |
| case ETextAlign::kStart: |
| if (!current_style.IsLeftToRightDirection()) |
| alignment = kAlignRight; |
| break; |
| case ETextAlign::kEnd: |
| if (current_style.IsLeftToRightDirection()) |
| alignment = kAlignRight; |
| break; |
| } |
| |
| LayoutUnit x = BorderLeft() + PaddingLeft(); |
| LayoutUnit max_x = width - BorderRight() - PaddingRight(); |
| BoxStrut border_padding = |
| (BorderOutsets() + PaddingOutsets()) |
| .ConvertToLogical( |
| {current_style.GetWritingMode(), TextDirection::kLtr}); |
| x = border_padding.inline_start; |
| max_x = width - border_padding.inline_end; |
| LayoutUnit caret_width = GetFrameView()->CaretWidth(); |
| |
| switch (alignment) { |
| case kAlignLeft: |
| if (current_style.IsLeftToRightDirection()) |
| x += text_indent_offset; |
| break; |
| case kAlignCenter: |
| x = (x + max_x) / 2; |
| if (current_style.IsLeftToRightDirection()) |
| x += text_indent_offset / 2; |
| else |
| x -= text_indent_offset / 2; |
| break; |
| case kAlignRight: |
| x = max_x - caret_width; |
| if (!current_style.IsLeftToRightDirection()) |
| x -= text_indent_offset; |
| break; |
| } |
| x = std::min(x, (max_x - caret_width).ClampNegativeToZero()); |
| |
| const Font& font = StyleRef().GetFont(); |
| const SimpleFontData* font_data = font.PrimaryFont(); |
| LayoutUnit height; |
| // crbug.com/595692 This check should not be needed but sometimes |
| // primaryFont is null. |
| if (font_data) |
| height = LayoutUnit(font_data->GetFontMetrics().Height()); |
| LayoutUnit vertical_space = FirstLineHeight() - height; |
| LayoutUnit block_start = border_padding.block_start + (vertical_space / 2); |
| return LogicalRect(x, block_start, caret_width, height); |
| } |
| |
| void LayoutBoxModelObject::MoveChildTo( |
| LayoutBoxModelObject* to_box_model_object, |
| LayoutObject* child, |
| LayoutObject* before_child, |
| bool full_remove_insert) { |
| NOT_DESTROYED(); |
| DCHECK_EQ(this, child->Parent()); |
| DCHECK(!before_child || to_box_model_object == before_child->Parent()); |
| |
| if (full_remove_insert && (to_box_model_object->IsLayoutBlock() || |
| to_box_model_object->IsLayoutInline())) { |
| // Takes care of adding the new child correctly if toBlock and fromBlock |
| // have different kind of children (block vs inline). |
| to_box_model_object->AddChild( |
| VirtualChildren()->RemoveChildNode(this, child), before_child); |
| } else { |
| to_box_model_object->VirtualChildren()->InsertChildNode( |
| to_box_model_object, |
| VirtualChildren()->RemoveChildNode(this, child, full_remove_insert), |
| before_child, full_remove_insert); |
| } |
| } |
| |
| void LayoutBoxModelObject::MoveChildrenTo( |
| LayoutBoxModelObject* to_box_model_object, |
| LayoutObject* start_child, |
| LayoutObject* end_child, |
| LayoutObject* before_child, |
| bool full_remove_insert) { |
| NOT_DESTROYED(); |
| |
| DCHECK(!before_child || to_box_model_object == before_child->Parent()); |
| for (LayoutObject* child = start_child; child && child != end_child;) { |
| // Save our next sibling as moveChildTo will clear it. |
| LayoutObject* next_sibling = child->NextSibling(); |
| MoveChildTo(to_box_model_object, child, before_child, full_remove_insert); |
| child = next_sibling; |
| } |
| } |
| |
| LayoutObject* LayoutBoxModelObject::SplitAnonymousBoxesAroundChild( |
| LayoutObject* before_child) { |
| NOT_DESTROYED(); |
| LayoutBox* box_at_top_of_new_branch = nullptr; |
| |
| while (before_child->Parent() != this) { |
| auto* box_to_split = To<LayoutBox>(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 = CreateAnonymousBoxToSplit(box_to_split); |
| post_box->SetChildrenInline(box_to_split->ChildrenInline()); |
| auto* parent_box = To<LayoutBoxModelObject>(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. |
| 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; |
| } |
| } |
| |
| if (box_at_top_of_new_branch) { |
| MarkBoxForRelayoutAfterSplit(this); |
| } |
| |
| DCHECK_EQ(before_child->Parent(), this); |
| return before_child; |
| } |
| |
| LayoutBox* LayoutBoxModelObject::CreateAnonymousBoxToSplit( |
| const LayoutBox* box_to_split) const { |
| NOT_DESTROYED(); |
| return box_to_split->CreateAnonymousBoxWithSameTypeAs(this); |
| } |
| |
| bool LayoutBoxModelObject::BackgroundTransfersToView( |
| const ComputedStyle* document_element_style) const { |
| NOT_DESTROYED(); |
| // In our painter implementation, ViewPainter instead of the painter of the |
| // layout object of the document element paints the view background. |
| if (IsDocumentElement()) |
| return true; |
| |
| // http://www.w3.org/TR/css3-background/#body-background |
| // If the document element is <html> with no background, and a <body> child |
| // element exists, the <body> element's background transfers to the document |
| // element which in turn transfers to the view in our painter implementation. |
| if (!IsBody()) |
| return false; |
| |
| Element* document_element = GetDocument().documentElement(); |
| if (!IsA<HTMLHtmlElement>(document_element)) |
| return false; |
| |
| if (!document_element_style) |
| document_element_style = document_element->GetComputedStyle(); |
| DCHECK(document_element_style); |
| if (document_element_style->HasBackground()) |
| return false; |
| if (GetNode() != GetDocument().FirstBodyElement()) |
| return false; |
| if (document_element_style->ShouldApplyAnyContainment(*document_element)) |
| return false; |
| if (StyleRef().ShouldApplyAnyContainment(*To<Element>(GetNode()))) |
| return false; |
| return true; |
| } |
| |
| } // namespace blink |