| /* |
| * This file is part of the layout object implementation for KHTML. |
| * |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * Copyright (C) 2003 Apple Computer, Inc. |
| * |
| * 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_deprecated_flexible_box.h" |
| |
| #include <algorithm> |
| #include "third_party/blink/renderer/core/frame/web_feature.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/layout_view.h" |
| #include "third_party/blink/renderer/core/layout/text_autosizer.h" |
| #include "third_party/blink/renderer/core/layout/text_run_constructor.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/platform/fonts/font.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/character_names.h" |
| |
| namespace blink { |
| |
| class FlexBoxIterator { |
| public: |
| FlexBoxIterator(LayoutDeprecatedFlexibleBox* parent) |
| : box_(parent), largest_ordinal_(1) { |
| forward_ = box_->StyleRef().BoxDirection() == EBoxDirection::kNormal; |
| if (!forward_) { |
| // No choice, since we're going backwards, we have to find out the highest |
| // ordinal up front. |
| LayoutBox* child = box_->FirstChildBox(); |
| while (child) { |
| if (child->StyleRef().BoxOrdinalGroup() > largest_ordinal_) |
| largest_ordinal_ = child->StyleRef().BoxOrdinalGroup(); |
| child = child->NextSiblingBox(); |
| } |
| } |
| |
| Reset(); |
| } |
| |
| void Reset() { |
| current_child_ = nullptr; |
| natural_current_child_ = nullptr; |
| ordinal_iteration_ = -1; |
| } |
| |
| LayoutBox* First() { |
| Reset(); |
| return Next(); |
| } |
| |
| LayoutBox* Next() { |
| do { |
| if (!current_child_) { |
| ++ordinal_iteration_; |
| |
| if (!ordinal_iteration_) { |
| current_ordinal_ = forward_ ? 1 : largest_ordinal_; |
| } else { |
| if (static_cast<size_t>(ordinal_iteration_) >= |
| ordinal_values_.size() + 1) |
| return nullptr; |
| |
| // Only copy+sort the values once per layout even if the iterator is |
| // reset. |
| if (ordinal_values_.size() != sorted_ordinal_values_.size()) { |
| CopyToVector(ordinal_values_, sorted_ordinal_values_); |
| std::sort(sorted_ordinal_values_.begin(), |
| sorted_ordinal_values_.end()); |
| } |
| current_ordinal_ = |
| forward_ ? sorted_ordinal_values_[ordinal_iteration_ - 1] |
| : sorted_ordinal_values_[sorted_ordinal_values_.size() - |
| ordinal_iteration_]; |
| } |
| |
| current_child_ = |
| forward_ ? box_->FirstChildBox() : box_->LastChildBox(); |
| } else { |
| current_child_ = forward_ ? current_child_->NextSiblingBox() |
| : current_child_->PreviousSiblingBox(); |
| } |
| |
| if (current_child_ && NotFirstOrdinalValue()) |
| ordinal_values_.insert(current_child_->StyleRef().BoxOrdinalGroup()); |
| } while (!current_child_ || (!current_child_->IsAnonymous() && |
| current_child_->StyleRef().BoxOrdinalGroup() != |
| current_ordinal_)); |
| |
| // This peice of code just exists for detecting if this iterator actually |
| // does something other than returning the default order. |
| if (!natural_current_child_) |
| natural_current_child_ = box_->FirstChildBox(); |
| else |
| natural_current_child_ = natural_current_child_->NextSiblingBox(); |
| |
| if (natural_current_child_ != current_child_) { |
| UseCounter::Count(box_->GetDocument(), |
| WebFeature::kWebkitBoxNotDefaultOrder); |
| } |
| |
| return current_child_; |
| } |
| |
| private: |
| bool NotFirstOrdinalValue() { |
| unsigned first_ordinal_value = forward_ ? 1 : largest_ordinal_; |
| return current_ordinal_ == first_ordinal_value && |
| current_child_->StyleRef().BoxOrdinalGroup() != first_ordinal_value; |
| } |
| |
| LayoutDeprecatedFlexibleBox* box_; |
| LayoutBox* current_child_; |
| LayoutBox* natural_current_child_; |
| bool forward_; |
| unsigned current_ordinal_; |
| unsigned largest_ordinal_; |
| HashSet<unsigned> ordinal_values_; |
| Vector<unsigned> sorted_ordinal_values_; |
| int ordinal_iteration_; |
| }; |
| |
| // Helper methods for obtaining the last line, computing line counts and heights |
| // for line counts |
| // (crawling into blocks). |
| static bool ShouldCheckLines(LayoutBlockFlow* block_flow) { |
| return !block_flow->IsFloatingOrOutOfFlowPositioned() && |
| block_flow->StyleRef().Height().IsAuto(); |
| } |
| |
| static int GetHeightForLineCount(const LayoutBlockFlow* block_flow, |
| int line_count, |
| bool include_bottom, |
| int& count) { |
| if (block_flow->ChildrenInline()) { |
| for (RootInlineBox* box = block_flow->FirstRootBox(); box; |
| box = box->NextRootBox()) { |
| if (++count == line_count) |
| return (box->LineBottomWithLeading() + |
| (include_bottom ? (block_flow->BorderBottom() + |
| block_flow->PaddingBottom()) |
| : LayoutUnit())) |
| .ToInt(); |
| } |
| return -1; |
| } |
| |
| LayoutBox* normal_flow_child_without_lines = nullptr; |
| for (LayoutBox* obj = block_flow->FirstChildBox(); obj; |
| obj = obj->NextSiblingBox()) { |
| auto* block_flow = DynamicTo<LayoutBlockFlow>(obj); |
| if (block_flow && ShouldCheckLines(block_flow)) { |
| int result = GetHeightForLineCount(block_flow, line_count, false, count); |
| if (result != -1) |
| return (result + obj->Location().Y() + |
| (include_bottom ? (block_flow->BorderBottom() + |
| block_flow->PaddingBottom()) |
| : LayoutUnit())) |
| .ToInt(); |
| } else if (!obj->IsFloatingOrOutOfFlowPositioned()) { |
| normal_flow_child_without_lines = obj; |
| } |
| } |
| if (normal_flow_child_without_lines && line_count == 0) |
| return (normal_flow_child_without_lines->Location().Y() + |
| normal_flow_child_without_lines->Size().Height()) |
| .ToInt(); |
| |
| return -1; |
| } |
| |
| static RootInlineBox* LineAtIndex(const LayoutBlockFlow* block_flow, int i) { |
| DCHECK_GE(i, 0); |
| |
| if (block_flow->ChildrenInline()) { |
| for (RootInlineBox* box = block_flow->FirstRootBox(); box; |
| box = box->NextRootBox()) { |
| if (!i--) |
| return box; |
| } |
| return nullptr; |
| } |
| for (LayoutObject* child = block_flow->FirstChild(); child; |
| child = child->NextSibling()) { |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(child); |
| if (!child_block_flow) |
| continue; |
| if (!ShouldCheckLines(child_block_flow)) |
| continue; |
| if (RootInlineBox* box = LineAtIndex(child_block_flow, i)) |
| return box; |
| } |
| |
| return nullptr; |
| } |
| |
| static int LineCount(const LayoutBlockFlow* block_flow, |
| const RootInlineBox* stop_root_inline_box = nullptr, |
| bool* found = nullptr) { |
| int count = 0; |
| if (block_flow->ChildrenInline()) { |
| for (RootInlineBox* box = block_flow->FirstRootBox(); box; |
| box = box->NextRootBox()) { |
| count++; |
| if (box == stop_root_inline_box) { |
| if (found) |
| *found = true; |
| break; |
| } |
| } |
| return count; |
| } |
| for (LayoutObject* obj = block_flow->FirstChild(); obj; |
| obj = obj->NextSibling()) { |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(obj); |
| if (!child_block_flow) |
| continue; |
| if (!ShouldCheckLines(child_block_flow)) |
| continue; |
| bool recursive_found = false; |
| count += |
| LineCount(child_block_flow, stop_root_inline_box, &recursive_found); |
| if (recursive_found) { |
| if (found) |
| *found = true; |
| break; |
| } |
| } |
| return count; |
| } |
| |
| static void ClearTruncation(LayoutBlockFlow* block_flow) { |
| if (block_flow->ChildrenInline() && block_flow->HasMarkupTruncation()) { |
| block_flow->SetHasMarkupTruncation(false); |
| for (RootInlineBox* box = block_flow->FirstRootBox(); box; |
| box = box->NextRootBox()) |
| box->ClearTruncation(); |
| return; |
| } |
| for (LayoutObject* obj = block_flow->FirstChild(); obj; |
| obj = obj->NextSibling()) { |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(obj); |
| if (!child_block_flow) |
| continue; |
| if (ShouldCheckLines(child_block_flow)) |
| ClearTruncation(child_block_flow); |
| } |
| } |
| |
| LayoutDeprecatedFlexibleBox::LayoutDeprecatedFlexibleBox(Element* element) |
| : LayoutBlock(element) { |
| DCHECK(!ChildrenInline()); |
| if (!IsAnonymous()) { |
| const KURL& url = GetDocument().Url(); |
| if (url.ProtocolIs("chrome")) { |
| UseCounter::Count(GetDocument(), WebFeature::kDeprecatedFlexboxChrome); |
| } else if (url.ProtocolIs("chrome-extension")) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kDeprecatedFlexboxChromeExtension); |
| } else { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kDeprecatedFlexboxWebContent); |
| } |
| } |
| } |
| |
| LayoutDeprecatedFlexibleBox::~LayoutDeprecatedFlexibleBox() = default; |
| |
| static LayoutUnit MarginWidthForChild(LayoutBox* child) { |
| // A margin basically has three types: fixed, percentage, and auto (variable). |
| // Auto and percentage margins simply become 0 when computing min/max width. |
| // Fixed margins can be added in as is. |
| const Length& margin_left = child->StyleRef().MarginLeft(); |
| const Length& margin_right = child->StyleRef().MarginRight(); |
| LayoutUnit margin; |
| if (margin_left.IsFixed()) |
| margin += margin_left.Value(); |
| if (margin_right.IsFixed()) |
| margin += margin_right.Value(); |
| return margin; |
| } |
| |
| static LayoutUnit HeightForChild(LayoutBox* child) { |
| if (child->HasOverrideLogicalHeight()) |
| return child->OverrideLogicalHeight(); |
| return child->LogicalHeight(); |
| } |
| |
| static LayoutUnit ContentHeightForChild(LayoutBox* child) { |
| // TODO(rego): Shouldn't we subtract the scrollbar height too? |
| return (HeightForChild(child) - child->BorderAndPaddingLogicalHeight()) |
| .ClampNegativeToZero(); |
| } |
| |
| void LayoutDeprecatedFlexibleBox::StyleWillChange( |
| StyleDifference diff, |
| const ComputedStyle& new_style) { |
| const ComputedStyle* old_style = Style(); |
| if (old_style && old_style->HasLineClamp() && !new_style.HasLineClamp()) |
| ClearLineClamp(); |
| |
| LayoutBlock::StyleWillChange(diff, new_style); |
| } |
| |
| MinMaxSizes LayoutDeprecatedFlexibleBox::ComputeIntrinsicLogicalWidths() const { |
| MinMaxSizes sizes; |
| for (LayoutBox* child = FirstChildBox(); child; |
| child = child->NextSiblingBox()) { |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| MinMaxSizes child_sizes = child->PreferredLogicalWidths(); |
| child_sizes += MarginWidthForChild(child); |
| |
| sizes.Encompass(child_sizes); |
| } |
| |
| sizes.max_size = std::max(sizes.min_size, sizes.max_size); |
| sizes += BorderAndPaddingLogicalWidth() + ScrollbarLogicalWidth(); |
| return sizes; |
| } |
| |
| void LayoutDeprecatedFlexibleBox::UpdateBlockLayout(bool relayout_children) { |
| DCHECK(NeedsLayout()); |
| DCHECK_EQ(StyleRef().BoxOrient(), EBoxOrient::kVertical); |
| |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxLayout); |
| |
| if (StyleRef().BoxAlign() != ComputedStyleInitialValues::InitialBoxAlign()) |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxAlignNotInitial); |
| |
| if (StyleRef().BoxDirection() != |
| ComputedStyleInitialValues::InitialBoxDirection()) |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxDirectionNotInitial); |
| |
| if (StyleRef().BoxPack() != ComputedStyleInitialValues::InitialBoxPack()) |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxPackNotInitial); |
| |
| if (!FirstChildBox()) { |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxNoChildren); |
| } else if (!FirstChildBox()->NextSiblingBox()) { |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxOneChild); |
| |
| auto* first_child_block_flow = DynamicTo<LayoutBlockFlow>(FirstChildBox()); |
| if (first_child_block_flow && first_child_block_flow->ChildrenInline()) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kWebkitBoxOneChildIsLayoutBlockFlowInline); |
| } |
| } else { |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxManyChildren); |
| } |
| |
| if (!relayout_children && SimplifiedLayout()) |
| return; |
| |
| { |
| // LayoutState needs this deliberate scope to pop before paint invalidation. |
| LayoutState state(*this); |
| |
| LayoutSize previous_size = Size(); |
| |
| UpdateLogicalWidth(); |
| UpdateLogicalHeight(); |
| |
| TextAutosizer::LayoutScope text_autosizer_layout_scope(this); |
| |
| if (previous_size != Size()) |
| relayout_children = true; |
| |
| SetHeight(LayoutUnit()); |
| |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxLayoutVertical); |
| LayoutVerticalBox(relayout_children); |
| |
| LayoutUnit old_client_after_edge = ClientLogicalBottom(); |
| UpdateLogicalHeight(); |
| |
| if (previous_size.Height() != Size().Height()) |
| relayout_children = true; |
| |
| LayoutPositionedObjects(relayout_children || IsDocumentElement()); |
| |
| ComputeLayoutOverflow(old_client_after_edge); |
| } |
| |
| UpdateAfterLayout(); |
| |
| ClearNeedsLayout(); |
| } |
| |
| // The first walk over our kids is to find out if we have any flexible children. |
| static void GatherFlexChildrenInfo(FlexBoxIterator& iterator, |
| Document& document, |
| bool relayout_children, |
| bool& have_flex) { |
| for (LayoutBox* child = iterator.First(); child; child = iterator.Next()) { |
| if (child->StyleRef().BoxFlex() != |
| ComputedStyleInitialValues::InitialBoxFlex()) |
| UseCounter::Count(document, WebFeature::kWebkitBoxChildFlexNotInitial); |
| |
| if (child->StyleRef().BoxOrdinalGroup() != |
| ComputedStyleInitialValues::InitialBoxOrdinalGroup()) { |
| UseCounter::Count(document, |
| WebFeature::kWebkitBoxChildOrdinalGroupNotInitial); |
| } |
| |
| // Check to see if this child flexes. |
| if (!child->IsOutOfFlowPositioned() && child->StyleRef().BoxFlex() > 0.0f) { |
| // We always have to lay out flexible objects again, since the flex |
| // distribution |
| // may have changed, and we need to reallocate space. |
| child->ClearOverrideSize(); |
| if (!relayout_children) |
| child->SetChildNeedsLayout(kMarkOnlyThis); |
| have_flex = true; |
| } |
| } |
| } |
| |
| void LayoutDeprecatedFlexibleBox::LayoutVerticalBox(bool relayout_children) { |
| LayoutUnit y_pos = BorderTop() + PaddingTop(); |
| LayoutUnit to_add = |
| BorderBottom() + PaddingBottom() + HorizontalScrollbarHeight(); |
| bool height_specified = false; |
| bool paginated = View()->GetLayoutState()->IsPaginated(); |
| LayoutUnit old_height; |
| |
| LayoutUnit remaining_space; |
| |
| FlexBoxIterator iterator(this); |
| bool have_flex = false, flexing_children = false; |
| GatherFlexChildrenInfo(iterator, GetDocument(), relayout_children, have_flex); |
| |
| // We confine the line clamp ugliness to vertical flexible boxes (thus keeping |
| // it out of |
| // mainstream block layout); this is not really part of the XUL box model. |
| if (StyleRef().HasLineClamp()) |
| ApplyLineClamp(iterator, relayout_children); |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope delay_clamp_scope; |
| |
| // We do 2 passes. The first pass is simply to lay everyone out at |
| // their preferred widths. The second pass handles flexing the children. |
| // Our first pass is done without flexing. We simply lay the children |
| // out within the box. |
| do { |
| SetHeight(BorderTop() + PaddingTop()); |
| LayoutUnit min_height = Size().Height() + to_add; |
| |
| for (LayoutBox* child = iterator.First(); child; child = iterator.Next()) { |
| if (child->IsOutOfFlowPositioned()) { |
| child->ContainingBlock()->InsertPositionedObject(child); |
| PaintLayer* child_layer = child->Layer(); |
| child_layer->SetStaticInlinePosition(BorderStart() + PaddingStart()); |
| if (child_layer->StaticBlockPosition() != Size().Height()) { |
| child_layer->SetStaticBlockPosition(Size().Height()); |
| if (child->StyleRef().HasStaticBlockPosition( |
| StyleRef().IsHorizontalWritingMode())) |
| child->SetChildNeedsLayout(kMarkOnlyThis); |
| } |
| continue; |
| } |
| |
| SubtreeLayoutScope layout_scope(*child); |
| if (!StyleRef().HasLineClamp() && |
| (relayout_children || |
| (child->IsAtomicInlineLevel() && |
| (child->StyleRef().Width().IsPercentOrCalc() || |
| child->StyleRef().Height().IsPercentOrCalc())))) |
| layout_scope.SetChildNeedsLayout(child); |
| |
| // Compute the child's vertical margins. |
| child->ComputeAndSetBlockDirectionMargins(this); |
| |
| // Add in the child's marginTop to our height. |
| SetHeight(Size().Height() + child->MarginTop()); |
| |
| if (!child->NeedsLayout()) |
| MarkChildForPaginationRelayoutIfNeeded(*child, layout_scope); |
| |
| // Now do a layout. |
| child->LayoutIfNeeded(); |
| |
| // We can place the child now, using our value of box-align. |
| LayoutUnit child_x = BorderLeft() + PaddingLeft(); |
| switch (StyleRef().BoxAlign()) { |
| case EBoxAlignment::kCenter: |
| case EBoxAlignment::kBaseline: // Baseline just maps to center for |
| // vertical boxes |
| child_x += child->MarginLeft() + |
| ((ContentWidth() - |
| (child->Size().Width() + child->MarginWidth())) / |
| 2) |
| .ClampNegativeToZero(); |
| break; |
| case EBoxAlignment::kEnd: |
| if (!StyleRef().IsLeftToRightDirection()) { |
| child_x += child->MarginLeft(); |
| } else { |
| child_x += |
| ContentWidth() - child->MarginRight() - child->Size().Width(); |
| } |
| break; |
| default: // BSTART/BSTRETCH |
| if (StyleRef().IsLeftToRightDirection()) { |
| child_x += child->MarginLeft(); |
| } else { |
| child_x += |
| ContentWidth() - child->MarginRight() - child->Size().Width(); |
| } |
| break; |
| } |
| |
| // Place the child. |
| PlaceChild(child, LayoutPoint(child_x, Size().Height())); |
| SetHeight(Size().Height() + child->Size().Height() + |
| child->MarginBottom()); |
| |
| if (paginated) |
| UpdateFragmentationInfoForChild(*child); |
| } |
| |
| y_pos = Size().Height(); |
| |
| if (!iterator.First() && HasLineIfEmpty()) { |
| SetHeight(Size().Height() + |
| LineHeight(true, |
| StyleRef().IsHorizontalWritingMode() |
| ? kHorizontalLine |
| : kVerticalLine, |
| kPositionOfInteriorLineBoxes)); |
| } |
| |
| SetHeight(Size().Height() + to_add); |
| |
| // Negative margins can cause our height to shrink below our minimal height |
| // (border/padding). If this happens, ensure that the computed height is |
| // increased to the minimal height. |
| if (Size().Height() < min_height) |
| SetHeight(min_height); |
| |
| // Now we have to calc our height, so we know how much space we have |
| // remaining. |
| old_height = Size().Height(); |
| UpdateLogicalHeight(); |
| if (old_height != Size().Height()) |
| height_specified = true; |
| |
| remaining_space = Size().Height() - BorderBottom() - PaddingBottom() - |
| HorizontalScrollbarHeight() - y_pos; |
| |
| if (flexing_children) { |
| have_flex = false; // We're done. |
| } else if (have_flex) { |
| // We have some flexible objects. See if we need to grow/shrink them at |
| // all. |
| if (!remaining_space) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. |
| bool expanding = remaining_space > 0; |
| do { |
| // Flexing consists of multiple passes, since we have to change |
| // ratios every time an object hits its max/min-width For a given |
| // pass, we always start off by computing the totalFlex of all |
| // objects that can grow/shrink at all, and computing the allowed |
| // growth before an object hits its min/max width (and thus forces a |
| // totalFlex recomputation). |
| LayoutUnit remaining_space_at_beginning = remaining_space; |
| float total_flex = 0.0f; |
| for (LayoutBox* child = iterator.First(); child; |
| child = iterator.Next()) { |
| if (AllowedChildFlex(child, expanding)) |
| total_flex += child->StyleRef().BoxFlex(); |
| } |
| LayoutUnit space_available_this_pass = remaining_space; |
| for (LayoutBox* child = iterator.First(); child; |
| child = iterator.Next()) { |
| LayoutUnit allowed_flex = AllowedChildFlex(child, expanding); |
| if (allowed_flex) { |
| LayoutUnit projected_flex = |
| (allowed_flex == LayoutUnit::Max()) |
| ? allowed_flex |
| : static_cast<LayoutUnit>( |
| allowed_flex * |
| (total_flex / child->StyleRef().BoxFlex())); |
| space_available_this_pass = |
| expanding ? std::min(space_available_this_pass, projected_flex) |
| : std::max(space_available_this_pass, projected_flex); |
| } |
| } |
| |
| // If we can't grow/shrink anymore, break. |
| if (!space_available_this_pass || total_flex == 0.0f) |
| break; |
| |
| // Now distribute the space to objects. |
| for (LayoutBox* child = iterator.First(); |
| child && space_available_this_pass && total_flex; |
| child = iterator.Next()) { |
| if (AllowedChildFlex(child, expanding)) { |
| LayoutUnit space_add = static_cast<LayoutUnit>( |
| space_available_this_pass * |
| (child->StyleRef().BoxFlex() / total_flex)); |
| if (space_add) { |
| child->SetOverrideLogicalHeight(HeightForChild(child) + |
| space_add); |
| flexing_children = true; |
| relayout_children = true; |
| } |
| |
| space_available_this_pass -= space_add; |
| remaining_space -= space_add; |
| |
| total_flex -= child->StyleRef().BoxFlex(); |
| } |
| } |
| if (remaining_space == remaining_space_at_beginning) { |
| // This is not advancing, avoid getting stuck by distributing the |
| // remaining pixels. |
| LayoutUnit space_add = LayoutUnit(remaining_space > 0 ? 1 : -1); |
| for (LayoutBox* child = iterator.First(); child && remaining_space; |
| child = iterator.Next()) { |
| if (AllowedChildFlex(child, expanding)) { |
| child->SetOverrideLogicalHeight(HeightForChild(child) + |
| space_add); |
| flexing_children = true; |
| relayout_children = true; |
| remaining_space -= space_add; |
| } |
| } |
| } |
| } while (AbsoluteValue(remaining_space) >= 1); |
| |
| // We didn't find any children that could grow. |
| if (have_flex && !flexing_children) |
| have_flex = false; |
| } |
| } while (have_flex); |
| |
| if (StyleRef().BoxPack() != EBoxPack::kStart && remaining_space > 0) { |
| // Children must be repositioned. |
| LayoutUnit offset; |
| if (StyleRef().BoxPack() == EBoxPack::kJustify) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kWebkitBoxPackJustifyDoesSomething); |
| // Determine the total number of children. |
| int total_children = 0; |
| for (LayoutBox* child = iterator.First(); child; |
| child = iterator.Next()) { |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| ++total_children; |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (total_children > 1) { |
| --total_children; |
| bool first_child = true; |
| for (LayoutBox* child = iterator.First(); child; |
| child = iterator.Next()) { |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| if (first_child) { |
| first_child = false; |
| continue; |
| } |
| |
| offset += remaining_space / total_children; |
| remaining_space -= (remaining_space / total_children); |
| --total_children; |
| PlaceChild(child, |
| child->Location() + LayoutSize(LayoutUnit(), offset)); |
| } |
| } |
| } else { |
| if (StyleRef().BoxPack() == EBoxPack::kCenter) { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kWebkitBoxPackCenterDoesSomething); |
| offset += remaining_space / 2; |
| } else { // END |
| UseCounter::Count(GetDocument(), |
| WebFeature::kWebkitBoxPackEndDoesSomething); |
| offset += remaining_space; |
| } |
| for (LayoutBox* child = iterator.First(); child; |
| child = iterator.Next()) { |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| PlaceChild(child, child->Location() + LayoutSize(LayoutUnit(), offset)); |
| } |
| } |
| } |
| |
| // So that the computeLogicalHeight in layoutBlock() knows to relayout |
| // positioned objects because of a height change, we revert our height back |
| // to the intrinsic height before returning. |
| if (height_specified) |
| SetHeight(old_height); |
| } |
| |
| void LayoutDeprecatedFlexibleBox::ApplyLineClamp(FlexBoxIterator& iterator, |
| bool relayout_children) { |
| UseCounter::Count(GetDocument(), WebFeature::kLineClamp); |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxLineClamp); |
| |
| LayoutBox* child = FirstChildBox(); |
| if (!child) { |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxLineClampNoChildren); |
| } else if (!child->NextSiblingBox()) { |
| UseCounter::Count(GetDocument(), WebFeature::kWebkitBoxLineClampOneChild); |
| |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(child); |
| if (child_block_flow && child_block_flow->ChildrenInline()) { |
| UseCounter::Count( |
| GetDocument(), |
| WebFeature::kWebkitBoxLineClampOneChildIsLayoutBlockFlowInline); |
| } |
| } else { |
| UseCounter::Count(GetDocument(), |
| WebFeature::kWebkitBoxLineClampManyChildren); |
| } |
| |
| int max_line_count = 0; |
| for (LayoutBox* child = iterator.First(); child; child = iterator.Next()) { |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| child->ClearOverrideSize(); |
| if (relayout_children || |
| (child->IsAtomicInlineLevel() && |
| (child->StyleRef().Width().IsPercentOrCalc() || |
| child->StyleRef().Height().IsPercentOrCalc())) || |
| (child->StyleRef().Height().IsAuto() && child->IsLayoutBlock())) { |
| child->SetChildNeedsLayout(kMarkOnlyThis); |
| |
| // Dirty all the positioned objects. |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(child); |
| if (child_block_flow) { |
| child_block_flow->MarkPositionedObjectsForLayout(); |
| ClearTruncation(child_block_flow); |
| } |
| } |
| child->LayoutIfNeeded(); |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(child); |
| if (child->StyleRef().Height().IsAuto() && child_block_flow) { |
| max_line_count = std::max(max_line_count, LineCount(child_block_flow)); |
| } |
| } |
| |
| // Get the number of lines and then alter all block flow children with auto |
| // height to use the |
| // specified height. We always try to leave room for at least one line. |
| int num_visible_lines = StyleRef().LineClamp(); |
| DCHECK_GT(num_visible_lines, 0); |
| |
| if (num_visible_lines >= max_line_count) |
| return; |
| |
| for (LayoutBox* child = iterator.First(); child; child = iterator.Next()) { |
| auto* block_child = DynamicTo<LayoutBlockFlow>(child); |
| if (child->IsOutOfFlowPositioned() || |
| !child->StyleRef().Height().IsAuto() || !block_child) |
| continue; |
| |
| int line_count = blink::LineCount(block_child); |
| if (line_count <= num_visible_lines) |
| continue; |
| |
| int dummy_count = 0; |
| LayoutUnit new_height(GetHeightForLineCount(block_child, num_visible_lines, |
| true, dummy_count)); |
| if (new_height == child->Size().Height()) |
| continue; |
| |
| child->SetOverrideLogicalHeight(new_height); |
| child->ForceLayout(); |
| |
| // FIXME: For now don't support RTL. |
| if (StyleRef().Direction() != TextDirection::kLtr) |
| continue; |
| |
| // Get the last line |
| RootInlineBox* last_line = LineAtIndex(block_child, line_count - 1); |
| if (!last_line) |
| continue; |
| |
| RootInlineBox* last_visible_line = |
| LineAtIndex(block_child, num_visible_lines - 1); |
| if (!last_visible_line) |
| continue; |
| |
| DEFINE_STATIC_LOCAL(AtomicString, ellipsis_str, |
| (&kHorizontalEllipsisCharacter, 1)); |
| const Font& font = Style(num_visible_lines == 1)->GetFont(); |
| float total_width = |
| font.Width(ConstructTextRun(font, &kHorizontalEllipsisCharacter, 1, |
| StyleRef(), StyleRef().Direction())); |
| |
| // See if this width can be accommodated on the last visible line |
| LineLayoutBlockFlow dest_block = last_visible_line->Block(); |
| LineLayoutBlockFlow src_block = last_line->Block(); |
| |
| // FIXME: Directions of src/destBlock could be different from our direction |
| // and from one another. |
| if (!src_block.StyleRef().IsLeftToRightDirection()) |
| continue; |
| |
| bool left_to_right = dest_block.StyleRef().IsLeftToRightDirection(); |
| if (!left_to_right) |
| continue; |
| |
| LayoutUnit block_right_edge = dest_block.LogicalRightOffsetForLine( |
| last_visible_line->Y(), kDoNotIndentText); |
| if (!last_visible_line->LineCanAccommodateEllipsis( |
| left_to_right, block_right_edge, |
| last_visible_line->X() + last_visible_line->LogicalWidth(), |
| LayoutUnit(total_width))) |
| continue; |
| |
| UseCounter::Count(GetDocument(), |
| WebFeature::kWebkitBoxLineClampDoesSomething); |
| |
| // Let the truncation code kick in. |
| // FIXME: the text alignment should be recomputed after the width changes |
| // due to truncation. |
| LayoutUnit block_left_edge = dest_block.LogicalLeftOffsetForLine( |
| last_visible_line->Y(), kDoNotIndentText); |
| InlineBox* box_truncation_starts_at = nullptr; |
| last_visible_line->PlaceEllipsis(ellipsis_str, left_to_right, |
| block_left_edge, block_right_edge, |
| LayoutUnit(total_width), LayoutUnit(), |
| &box_truncation_starts_at, ForceEllipsis); |
| dest_block.SetHasMarkupTruncation(true); |
| } |
| } |
| |
| void LayoutDeprecatedFlexibleBox::ClearLineClamp() { |
| FlexBoxIterator iterator(this); |
| for (LayoutBox* child = iterator.First(); child; child = iterator.Next()) { |
| if (child->IsOutOfFlowPositioned()) |
| continue; |
| |
| child->ClearOverrideSize(); |
| if ((child->IsAtomicInlineLevel() && |
| (child->StyleRef().Width().IsPercentOrCalc() || |
| child->StyleRef().Height().IsPercentOrCalc())) || |
| (child->StyleRef().Height().IsAuto() && child->IsLayoutBlock())) { |
| child->SetChildNeedsLayout(); |
| |
| auto* child_block_flow = DynamicTo<LayoutBlockFlow>(child); |
| if (child_block_flow) { |
| child_block_flow->MarkPositionedObjectsForLayout(); |
| ClearTruncation(child_block_flow); |
| } |
| } |
| } |
| } |
| |
| void LayoutDeprecatedFlexibleBox::PlaceChild(LayoutBox* child, |
| const LayoutPoint& location) { |
| // FIXME Investigate if this can be removed based on other flags. |
| // crbug.com/370010 |
| child->SetShouldCheckForPaintInvalidation(); |
| |
| // Place the child. |
| child->SetLocation(location); |
| } |
| |
| LayoutUnit LayoutDeprecatedFlexibleBox::AllowedChildFlex(LayoutBox* child, |
| bool expanding) { |
| if (child->IsOutOfFlowPositioned() || child->StyleRef().BoxFlex() == 0.0f) |
| return LayoutUnit(); |
| |
| if (expanding) { |
| // FIXME: For now just handle fixed values. |
| LayoutUnit max_height = LayoutUnit::Max(); |
| LayoutUnit height = ContentHeightForChild(child); |
| if (child->StyleRef().MaxHeight().IsFixed()) |
| max_height = LayoutUnit(child->StyleRef().MaxHeight().Value()); |
| if (max_height == LayoutUnit::Max()) |
| return max_height; |
| return (max_height - height).ClampNegativeToZero(); |
| } |
| |
| // FIXME: For now just handle fixed values. |
| const Length& min_height_length = child->StyleRef().MinHeight(); |
| if (min_height_length.IsFixed() || min_height_length.IsAuto()) { |
| LayoutUnit min_height(min_height_length.Value()); |
| LayoutUnit height = ContentHeightForChild(child); |
| LayoutUnit allowed_shrinkage = (min_height - height).ClampPositiveToZero(); |
| return allowed_shrinkage; |
| } |
| return LayoutUnit(); |
| } |
| |
| } // namespace blink |