| /* |
| * 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 "core/layout/LayoutDeprecatedFlexibleBox.h" |
| |
| #include "core/frame/UseCounter.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/TextAutosizer.h" |
| #include "core/layout/TextRunConstructor.h" |
| #include "core/layout/api/LineLayoutBlockFlow.h" |
| #include "core/paint/PaintLayer.h" |
| #include "platform/fonts/Font.h" |
| #include "wtf/StdLibExtras.h" |
| #include "wtf/text/CharacterNames.h" |
| #include <algorithm> |
| |
| namespace blink { |
| |
| class FlexBoxIterator { |
| public: |
| FlexBoxIterator(LayoutDeprecatedFlexibleBox* parent) |
| : m_box(parent), m_largestOrdinal(1) { |
| if (m_box->style()->boxOrient() == HORIZONTAL && |
| !m_box->style()->isLeftToRightDirection()) |
| m_forward = m_box->style()->boxDirection() != EBoxDirection::Normal; |
| else |
| m_forward = m_box->style()->boxDirection() == EBoxDirection::Normal; |
| if (!m_forward) { |
| // No choice, since we're going backwards, we have to find out the highest |
| // ordinal up front. |
| LayoutBox* child = m_box->firstChildBox(); |
| while (child) { |
| if (child->style()->boxOrdinalGroup() > m_largestOrdinal) |
| m_largestOrdinal = child->style()->boxOrdinalGroup(); |
| child = child->nextSiblingBox(); |
| } |
| } |
| |
| reset(); |
| } |
| |
| void reset() { |
| m_currentChild = 0; |
| m_ordinalIteration = -1; |
| } |
| |
| LayoutBox* first() { |
| reset(); |
| return next(); |
| } |
| |
| LayoutBox* next() { |
| do { |
| if (!m_currentChild) { |
| ++m_ordinalIteration; |
| |
| if (!m_ordinalIteration) { |
| m_currentOrdinal = m_forward ? 1 : m_largestOrdinal; |
| } else { |
| if (static_cast<size_t>(m_ordinalIteration) >= |
| m_ordinalValues.size() + 1) |
| return nullptr; |
| |
| // Only copy+sort the values once per layout even if the iterator is |
| // reset. |
| if (m_ordinalValues.size() != m_sortedOrdinalValues.size()) { |
| copyToVector(m_ordinalValues, m_sortedOrdinalValues); |
| std::sort(m_sortedOrdinalValues.begin(), |
| m_sortedOrdinalValues.end()); |
| } |
| m_currentOrdinal = |
| m_forward ? m_sortedOrdinalValues[m_ordinalIteration - 1] |
| : m_sortedOrdinalValues[m_sortedOrdinalValues.size() - |
| m_ordinalIteration]; |
| } |
| |
| m_currentChild = |
| m_forward ? m_box->firstChildBox() : m_box->lastChildBox(); |
| } else { |
| m_currentChild = m_forward ? m_currentChild->nextSiblingBox() |
| : m_currentChild->previousSiblingBox(); |
| } |
| |
| if (m_currentChild && notFirstOrdinalValue()) |
| m_ordinalValues.add(m_currentChild->style()->boxOrdinalGroup()); |
| } while (!m_currentChild || |
| (!m_currentChild->isAnonymous() && |
| m_currentChild->style()->boxOrdinalGroup() != m_currentOrdinal)); |
| return m_currentChild; |
| } |
| |
| private: |
| bool notFirstOrdinalValue() { |
| unsigned firstOrdinalValue = m_forward ? 1 : m_largestOrdinal; |
| return m_currentOrdinal == firstOrdinalValue && |
| m_currentChild->style()->boxOrdinalGroup() != firstOrdinalValue; |
| } |
| |
| LayoutDeprecatedFlexibleBox* m_box; |
| LayoutBox* m_currentChild; |
| bool m_forward; |
| unsigned m_currentOrdinal; |
| unsigned m_largestOrdinal; |
| HashSet<unsigned> m_ordinalValues; |
| Vector<unsigned> m_sortedOrdinalValues; |
| int m_ordinalIteration; |
| }; |
| |
| // Helper methods for obtaining the last line, computing line counts and heights |
| // for line counts |
| // (crawling into blocks). |
| static bool shouldCheckLines(LayoutBlockFlow* blockFlow) { |
| return !blockFlow->isFloatingOrOutOfFlowPositioned() && |
| blockFlow->style()->height().isAuto(); |
| } |
| |
| static int getHeightForLineCount(const LayoutBlockFlow* blockFlow, |
| int lineCount, |
| bool includeBottom, |
| int& count) { |
| if (blockFlow->style()->visibility() != EVisibility::Visible) |
| return -1; |
| if (blockFlow->childrenInline()) { |
| for (RootInlineBox* box = blockFlow->firstRootBox(); box; |
| box = box->nextRootBox()) { |
| if (++count == lineCount) |
| return (box->lineBottom() + |
| (includeBottom |
| ? (blockFlow->borderBottom() + blockFlow->paddingBottom()) |
| : LayoutUnit())) |
| .toInt(); |
| } |
| return -1; |
| } |
| |
| LayoutBox* normalFlowChildWithoutLines = nullptr; |
| for (LayoutBox* obj = blockFlow->firstChildBox(); obj; |
| obj = obj->nextSiblingBox()) { |
| if (obj->isLayoutBlockFlow() && shouldCheckLines(toLayoutBlockFlow(obj))) { |
| int result = getHeightForLineCount(toLayoutBlockFlow(obj), lineCount, |
| false, count); |
| if (result != -1) |
| return (result + obj->location().y() + |
| (includeBottom |
| ? (blockFlow->borderBottom() + blockFlow->paddingBottom()) |
| : LayoutUnit())) |
| .toInt(); |
| } else if (!obj->isFloatingOrOutOfFlowPositioned()) { |
| normalFlowChildWithoutLines = obj; |
| } |
| } |
| if (normalFlowChildWithoutLines && lineCount == 0) |
| return (normalFlowChildWithoutLines->location().y() + |
| normalFlowChildWithoutLines->size().height()) |
| .toInt(); |
| |
| return -1; |
| } |
| |
| static RootInlineBox* lineAtIndex(const LayoutBlockFlow* blockFlow, int i) { |
| ASSERT(i >= 0); |
| |
| if (blockFlow->style()->visibility() != EVisibility::Visible) |
| return nullptr; |
| |
| if (blockFlow->childrenInline()) { |
| for (RootInlineBox* box = blockFlow->firstRootBox(); box; |
| box = box->nextRootBox()) { |
| if (!i--) |
| return box; |
| } |
| return nullptr; |
| } |
| for (LayoutObject* child = blockFlow->firstChild(); child; |
| child = child->nextSibling()) { |
| if (!child->isLayoutBlockFlow()) |
| continue; |
| LayoutBlockFlow* childBlockFlow = toLayoutBlockFlow(child); |
| if (!shouldCheckLines(childBlockFlow)) |
| continue; |
| if (RootInlineBox* box = lineAtIndex(childBlockFlow, i)) |
| return box; |
| } |
| |
| return nullptr; |
| } |
| |
| static int lineCount(const LayoutBlockFlow* blockFlow, |
| const RootInlineBox* stopRootInlineBox = nullptr, |
| bool* found = nullptr) { |
| if (blockFlow->style()->visibility() != EVisibility::Visible) |
| return 0; |
| int count = 0; |
| if (blockFlow->childrenInline()) { |
| for (RootInlineBox* box = blockFlow->firstRootBox(); box; |
| box = box->nextRootBox()) { |
| count++; |
| if (box == stopRootInlineBox) { |
| if (found) |
| *found = true; |
| break; |
| } |
| } |
| return count; |
| } |
| for (LayoutObject* obj = blockFlow->firstChild(); obj; |
| obj = obj->nextSibling()) { |
| if (!obj->isLayoutBlockFlow()) |
| continue; |
| LayoutBlockFlow* childBlockFlow = toLayoutBlockFlow(obj); |
| if (!shouldCheckLines(childBlockFlow)) |
| continue; |
| bool recursiveFound = false; |
| count += lineCount(childBlockFlow, stopRootInlineBox, &recursiveFound); |
| if (recursiveFound) { |
| if (found) |
| *found = true; |
| break; |
| } |
| } |
| return count; |
| } |
| |
| static void clearTruncation(LayoutBlockFlow* blockFlow) { |
| if (blockFlow->style()->visibility() != EVisibility::Visible) |
| return; |
| if (blockFlow->childrenInline() && blockFlow->hasMarkupTruncation()) { |
| blockFlow->setHasMarkupTruncation(false); |
| for (RootInlineBox* box = blockFlow->firstRootBox(); box; |
| box = box->nextRootBox()) |
| box->clearTruncation(); |
| return; |
| } |
| for (LayoutObject* obj = blockFlow->firstChild(); obj; |
| obj = obj->nextSibling()) { |
| if (!obj->isLayoutBlockFlow()) |
| continue; |
| LayoutBlockFlow* childBlockFlow = toLayoutBlockFlow(obj); |
| if (shouldCheckLines(childBlockFlow)) |
| clearTruncation(childBlockFlow); |
| } |
| } |
| |
| LayoutDeprecatedFlexibleBox::LayoutDeprecatedFlexibleBox(Element& element) |
| : LayoutBlock(&element) { |
| ASSERT(!childrenInline()); |
| m_stretchingChildren = false; |
| if (!isAnonymous()) { |
| const KURL& url = document().url(); |
| if (url.protocolIs("chrome")) |
| UseCounter::count(document(), UseCounter::DeprecatedFlexboxChrome); |
| else if (url.protocolIs("chrome-extension")) |
| UseCounter::count(document(), |
| UseCounter::DeprecatedFlexboxChromeExtension); |
| else |
| UseCounter::count(document(), UseCounter::DeprecatedFlexboxWebContent); |
| } |
| } |
| |
| LayoutDeprecatedFlexibleBox::~LayoutDeprecatedFlexibleBox() {} |
| |
| 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. |
| Length marginLeft = child->style()->marginLeft(); |
| Length marginRight = child->style()->marginRight(); |
| LayoutUnit margin; |
| if (marginLeft.isFixed()) |
| margin += marginLeft.value(); |
| if (marginRight.isFixed()) |
| margin += marginRight.value(); |
| return margin; |
| } |
| |
| static bool childDoesNotAffectWidthOrFlexing(LayoutObject* child) { |
| // Positioned children and collapsed children don't affect the min/max width. |
| return child->isOutOfFlowPositioned() || |
| child->style()->visibility() == EVisibility::Collapse; |
| } |
| |
| static LayoutUnit contentWidthForChild(LayoutBox* child) { |
| if (child->hasOverrideLogicalContentWidth()) |
| return child->overrideLogicalContentWidth(); |
| return child->logicalWidth() - child->borderAndPaddingLogicalWidth(); |
| } |
| |
| static LayoutUnit contentHeightForChild(LayoutBox* child) { |
| if (child->hasOverrideLogicalContentHeight()) |
| return child->overrideLogicalContentHeight(); |
| return child->logicalHeight() - child->borderAndPaddingLogicalHeight(); |
| } |
| |
| void LayoutDeprecatedFlexibleBox::styleWillChange( |
| StyleDifference diff, |
| const ComputedStyle& newStyle) { |
| const ComputedStyle* oldStyle = style(); |
| if (oldStyle && !oldStyle->lineClamp().isNone() && |
| newStyle.lineClamp().isNone()) |
| clearLineClamp(); |
| |
| LayoutBlock::styleWillChange(diff, newStyle); |
| } |
| |
| void LayoutDeprecatedFlexibleBox::computeIntrinsicLogicalWidths( |
| LayoutUnit& minLogicalWidth, |
| LayoutUnit& maxLogicalWidth) const { |
| if (hasMultipleLines() || isVertical()) { |
| for (LayoutBox* child = firstChildBox(); child; |
| child = child->nextSiblingBox()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| LayoutUnit margin = marginWidthForChild(child); |
| LayoutUnit width = child->minPreferredLogicalWidth() + margin; |
| minLogicalWidth = std::max(width, minLogicalWidth); |
| |
| width = child->maxPreferredLogicalWidth() + margin; |
| maxLogicalWidth = std::max(width, maxLogicalWidth); |
| } |
| } else { |
| for (LayoutBox* child = firstChildBox(); child; |
| child = child->nextSiblingBox()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| LayoutUnit margin = marginWidthForChild(child); |
| minLogicalWidth += child->minPreferredLogicalWidth() + margin; |
| maxLogicalWidth += child->maxPreferredLogicalWidth() + margin; |
| } |
| } |
| |
| maxLogicalWidth = std::max(minLogicalWidth, maxLogicalWidth); |
| |
| LayoutUnit scrollbarWidth(scrollbarLogicalWidth()); |
| maxLogicalWidth += scrollbarWidth; |
| minLogicalWidth += scrollbarWidth; |
| } |
| |
| void LayoutDeprecatedFlexibleBox::layoutBlock(bool relayoutChildren) { |
| ASSERT(needsLayout()); |
| |
| if (!relayoutChildren && simplifiedLayout()) |
| return; |
| |
| { |
| // LayoutState needs this deliberate scope to pop before paint invalidation. |
| LayoutState state(*this); |
| |
| LayoutSize previousSize = size(); |
| |
| updateLogicalWidth(); |
| updateLogicalHeight(); |
| |
| TextAutosizer::LayoutScope textAutosizerLayoutScope(this); |
| |
| if (previousSize != size() || |
| (parent()->isDeprecatedFlexibleBox() && |
| parent()->style()->boxOrient() == HORIZONTAL && |
| parent()->style()->boxAlign() == BSTRETCH)) |
| relayoutChildren = true; |
| |
| setHeight(LayoutUnit()); |
| |
| m_stretchingChildren = false; |
| |
| if (isHorizontal()) |
| layoutHorizontalBox(relayoutChildren); |
| else |
| layoutVerticalBox(relayoutChildren); |
| |
| LayoutUnit oldClientAfterEdge = clientLogicalBottom(); |
| updateLogicalHeight(); |
| |
| if (previousSize.height() != size().height()) |
| relayoutChildren = true; |
| |
| layoutPositionedObjects(relayoutChildren || isDocumentElement()); |
| |
| computeOverflow(oldClientAfterEdge); |
| } |
| |
| updateLayerTransformAfterLayout(); |
| updateAfterLayout(); |
| |
| clearNeedsLayout(); |
| } |
| |
| // The first walk over our kids is to find out if we have any flexible children. |
| static void gatherFlexChildrenInfo(FlexBoxIterator& iterator, |
| bool relayoutChildren, |
| unsigned& highestFlexGroup, |
| unsigned& lowestFlexGroup, |
| bool& haveFlex) { |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| // Check to see if this child flexes. |
| if (!childDoesNotAffectWidthOrFlexing(child) && |
| child->style()->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 (!relayoutChildren) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| haveFlex = true; |
| unsigned flexGroup = child->style()->boxFlexGroup(); |
| if (lowestFlexGroup == 0) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup < lowestFlexGroup) |
| lowestFlexGroup = flexGroup; |
| if (flexGroup > highestFlexGroup) |
| highestFlexGroup = flexGroup; |
| } |
| } |
| } |
| |
| void LayoutDeprecatedFlexibleBox::layoutHorizontalBox(bool relayoutChildren) { |
| LayoutUnit toAdd = |
| borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
| LayoutUnit yPos = borderTop() + paddingTop(); |
| LayoutUnit xPos = borderLeft() + paddingLeft(); |
| bool heightSpecified = false; |
| bool paginated = view()->layoutState()->isPaginated(); |
| LayoutUnit oldHeight; |
| |
| LayoutUnit remainingSpace; |
| |
| FlexBoxIterator iterator(this); |
| unsigned highestFlexGroup = 0; |
| unsigned lowestFlexGroup = 0; |
| bool haveFlex = false, flexingChildren = false; |
| gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, |
| lowestFlexGroup, haveFlex); |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope delayClampScope; |
| |
| // We do 2 passes. The first pass is simply to lay everyone out at |
| // their preferred widths. The second pass handles flexing the children. |
| do { |
| // Reset our height. |
| setHeight(yPos); |
| |
| xPos = borderLeft() + paddingLeft(); |
| |
| // Our first pass is done without flexing. We simply lay the children |
| // out within the box. We have to do a layout first in order to determine |
| // our box's intrinsic height. |
| LayoutUnit maxAscent; |
| LayoutUnit maxDescent; |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| if (child->isOutOfFlowPositioned()) |
| continue; |
| |
| SubtreeLayoutScope layoutScope(*child); |
| // TODO(jchaffraix): It seems incorrect to check isAtomicInlineLevel in |
| // this file. |
| // We probably want to check if the element is replaced. |
| if (relayoutChildren || (child->isAtomicInlineLevel() && |
| (child->style()->width().isPercentOrCalc() || |
| child->style()->height().isPercentOrCalc()))) |
| layoutScope.setChildNeedsLayout(child); |
| |
| // Compute the child's vertical margins. |
| child->computeAndSetBlockDirectionMargins(this); |
| |
| if (!child->needsLayout()) |
| markChildForPaginationRelayoutIfNeeded(*child, layoutScope); |
| |
| // Now do the layout. |
| child->layoutIfNeeded(); |
| |
| // Update our height and overflow height. |
| if (style()->boxAlign() == BBASELINE) { |
| LayoutUnit ascent(child->firstLineBoxBaseline()); |
| if (ascent == -1) |
| ascent = child->size().height() + child->marginBottom(); |
| ascent += child->marginTop(); |
| LayoutUnit descent = |
| (child->size().height() + child->marginHeight()) - ascent; |
| |
| // Update our maximum ascent. |
| maxAscent = std::max(maxAscent, ascent); |
| |
| // Update our maximum descent. |
| maxDescent = std::max(maxDescent, descent); |
| |
| // Now update our height. |
| setHeight(std::max(yPos + maxAscent + maxDescent, size().height())); |
| } else { |
| setHeight(std::max(size().height(), yPos + child->size().height() + |
| child->marginHeight())); |
| } |
| |
| if (paginated) |
| updateFragmentationInfoForChild(*child); |
| } |
| |
| if (!iterator.first() && hasLineIfEmpty()) |
| setHeight(size().height() + lineHeight(true, |
| style()->isHorizontalWritingMode() |
| ? HorizontalLine |
| : VerticalLine, |
| PositionOfInteriorLineBoxes)); |
| |
| setHeight(size().height() + toAdd); |
| |
| oldHeight = size().height(); |
| updateLogicalHeight(); |
| |
| relayoutChildren = false; |
| if (oldHeight != size().height()) |
| heightSpecified = true; |
| |
| // Now that our height is actually known, we can place our boxes. |
| m_stretchingChildren = (style()->boxAlign() == BSTRETCH); |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| if (child->isOutOfFlowPositioned()) { |
| child->containingBlock()->insertPositionedObject(child); |
| PaintLayer* childLayer = child->layer(); |
| childLayer->setStaticInlinePosition(xPos); |
| if (childLayer->staticBlockPosition() != yPos) { |
| childLayer->setStaticBlockPosition(yPos); |
| if (child->style()->hasStaticBlockPosition( |
| style()->isHorizontalWritingMode())) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| } |
| continue; |
| } |
| |
| if (child->style()->visibility() == EVisibility::Collapse) { |
| // visibility: collapsed children do not participate in our positioning. |
| // But we need to lay them down. |
| child->layoutIfNeeded(); |
| continue; |
| } |
| |
| SubtreeLayoutScope layoutScope(*child); |
| |
| // We need to see if this child's height will change, since we make block |
| // elements fill the height of a containing box by default. We cannot |
| // actually *set* the new height here, though. Need to do that from |
| // within layout, or we won't be able to detect the change and duly |
| // notify any positioned descendants that are affected by it. |
| LayoutUnit oldChildHeight = child->logicalHeight(); |
| LogicalExtentComputedValues computedValues; |
| child->computeLogicalHeight(child->logicalHeight(), child->logicalTop(), |
| computedValues); |
| LayoutUnit newChildHeight = computedValues.m_extent; |
| if (oldChildHeight != newChildHeight) |
| layoutScope.setChildNeedsLayout(child); |
| |
| if (!child->needsLayout()) |
| markChildForPaginationRelayoutIfNeeded(*child, layoutScope); |
| |
| child->layoutIfNeeded(); |
| |
| // We can place the child now, using our value of box-align. |
| xPos += child->marginLeft(); |
| LayoutUnit childY = yPos; |
| switch (style()->boxAlign()) { |
| case BCENTER: |
| childY += child->marginTop() + |
| ((contentHeight() - |
| (child->size().height() + child->marginHeight())) / |
| 2).clampNegativeToZero(); |
| break; |
| case BBASELINE: { |
| LayoutUnit ascent(child->firstLineBoxBaseline()); |
| if (ascent == -1) |
| ascent = child->size().height() + child->marginBottom(); |
| ascent += child->marginTop(); |
| childY += child->marginTop() + (maxAscent - ascent); |
| break; |
| } |
| case BEND: |
| childY += |
| contentHeight() - child->marginBottom() - child->size().height(); |
| break; |
| default: // BSTART |
| childY += child->marginTop(); |
| break; |
| } |
| |
| placeChild(child, LayoutPoint(xPos, childY)); |
| |
| xPos += child->size().width() + child->marginRight(); |
| } |
| |
| remainingSpace = size().width() - borderRight() - paddingRight() - |
| verticalScrollbarWidth() - xPos; |
| |
| m_stretchingChildren = false; |
| if (flexingChildren) { |
| haveFlex = false; // We're done. |
| } else if (haveFlex) { |
| // We have some flexible objects. See if we need to grow/shrink them at |
| // all. |
| if (!remainingSpace) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. If we are |
| // trying to grow, then we go from the lowest flex group to the highest |
| // flex group. For shrinking, we go from the highest flex group to the |
| // lowest group. |
| bool expanding = remainingSpace > 0; |
| unsigned start = expanding ? lowestFlexGroup : highestFlexGroup; |
| unsigned end = expanding ? highestFlexGroup : lowestFlexGroup; |
| for (unsigned i = start; i <= end && remainingSpace; i++) { |
| // Always start off by assuming the group can get all the remaining |
| // space. |
| LayoutUnit groupRemainingSpace = remainingSpace; |
| 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 groupRemainingSpaceAtBeginning = groupRemainingSpace; |
| float totalFlex = 0.0f; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) |
| totalFlex += child->style()->boxFlex(); |
| } |
| LayoutUnit spaceAvailableThisPass = groupRemainingSpace; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| LayoutUnit allowedFlex = allowedChildFlex(child, expanding, i); |
| if (allowedFlex) { |
| LayoutUnit projectedFlex = |
| (allowedFlex == LayoutUnit::max()) |
| ? allowedFlex |
| : LayoutUnit(allowedFlex * |
| (totalFlex / child->style()->boxFlex())); |
| spaceAvailableThisPass = |
| expanding ? std::min(spaceAvailableThisPass, projectedFlex) |
| : std::max(spaceAvailableThisPass, projectedFlex); |
| } |
| } |
| |
| // The flex groups may not have any flexible objects this time around. |
| if (!spaceAvailableThisPass || totalFlex == 0.0f) { |
| // If we just couldn't grow/shrink any more, then it's time to |
| // transition to the next flex group. |
| groupRemainingSpace = LayoutUnit(); |
| continue; |
| } |
| |
| // Now distribute the space to objects. |
| for (LayoutBox* child = iterator.first(); |
| child && spaceAvailableThisPass && totalFlex; |
| child = iterator.next()) { |
| if (child->style()->visibility() == EVisibility::Collapse) |
| continue; |
| |
| if (allowedChildFlex(child, expanding, i)) { |
| LayoutUnit spaceAdd = |
| LayoutUnit(spaceAvailableThisPass * |
| (child->style()->boxFlex() / totalFlex)); |
| if (spaceAdd) { |
| child->setOverrideLogicalContentWidth( |
| contentWidthForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = true; |
| } |
| |
| spaceAvailableThisPass -= spaceAdd; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| |
| totalFlex -= child->style()->boxFlex(); |
| } |
| } |
| if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { |
| // This is not advancing, avoid getting stuck by distributing the |
| // remaining pixels. |
| LayoutUnit spaceAdd = LayoutUnit(groupRemainingSpace > 0 ? 1 : -1); |
| for (LayoutBox* child = iterator.first(); |
| child && groupRemainingSpace; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| child->setOverrideLogicalContentWidth( |
| contentWidthForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = true; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| } |
| } |
| } |
| } while (absoluteValue(groupRemainingSpace) >= 1); |
| } |
| |
| // We didn't find any children that could grow. |
| if (haveFlex && !flexingChildren) |
| haveFlex = false; |
| } |
| } while (haveFlex); |
| |
| if (remainingSpace > 0 && ((style()->isLeftToRightDirection() && |
| style()->boxPack() != BoxPackStart) || |
| (!style()->isLeftToRightDirection() && |
| style()->boxPack() != BoxPackEnd))) { |
| // Children must be repositioned. |
| LayoutUnit offset; |
| if (style()->boxPack() == BoxPackJustify) { |
| // Determine the total number of children. |
| int totalChildren = 0; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| ++totalChildren; |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (totalChildren > 1) { |
| --totalChildren; |
| bool firstChild = true; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| if (firstChild) { |
| firstChild = false; |
| continue; |
| } |
| |
| offset += remainingSpace / totalChildren; |
| remainingSpace -= (remainingSpace / totalChildren); |
| --totalChildren; |
| |
| placeChild(child, |
| child->location() + LayoutSize(offset, LayoutUnit())); |
| } |
| } |
| } else { |
| if (style()->boxPack() == BoxPackCenter) |
| offset += remainingSpace / 2; |
| else // END for LTR, START for RTL |
| offset += remainingSpace; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| placeChild(child, child->location() + LayoutSize(offset, LayoutUnit())); |
| } |
| } |
| } |
| |
| // 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 (heightSpecified) |
| setHeight(oldHeight); |
| } |
| |
| void LayoutDeprecatedFlexibleBox::layoutVerticalBox(bool relayoutChildren) { |
| LayoutUnit yPos = borderTop() + paddingTop(); |
| LayoutUnit toAdd = |
| borderBottom() + paddingBottom() + horizontalScrollbarHeight(); |
| bool heightSpecified = false; |
| bool paginated = view()->layoutState()->isPaginated(); |
| LayoutUnit oldHeight; |
| |
| LayoutUnit remainingSpace; |
| |
| FlexBoxIterator iterator(this); |
| unsigned highestFlexGroup = 0; |
| unsigned lowestFlexGroup = 0; |
| bool haveFlex = false, flexingChildren = false; |
| gatherFlexChildrenInfo(iterator, relayoutChildren, highestFlexGroup, |
| lowestFlexGroup, haveFlex); |
| |
| // 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. |
| bool haveLineClamp = !style()->lineClamp().isNone(); |
| if (haveLineClamp) |
| applyLineClamp(iterator, relayoutChildren); |
| |
| PaintLayerScrollableArea::DelayScrollOffsetClampScope delayClampScope; |
| |
| // 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 minHeight = size().height() + toAdd; |
| |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| if (child->isOutOfFlowPositioned()) { |
| child->containingBlock()->insertPositionedObject(child); |
| PaintLayer* childLayer = child->layer(); |
| childLayer->setStaticInlinePosition(borderStart() + paddingStart()); |
| if (childLayer->staticBlockPosition() != size().height()) { |
| childLayer->setStaticBlockPosition(size().height()); |
| if (child->style()->hasStaticBlockPosition( |
| style()->isHorizontalWritingMode())) |
| child->setChildNeedsLayout(MarkOnlyThis); |
| } |
| continue; |
| } |
| |
| SubtreeLayoutScope layoutScope(*child); |
| if (!haveLineClamp && |
| (relayoutChildren || (child->isAtomicInlineLevel() && |
| (child->style()->width().isPercentOrCalc() || |
| child->style()->height().isPercentOrCalc())))) |
| layoutScope.setChildNeedsLayout(child); |
| |
| if (child->style()->visibility() == EVisibility::Collapse) { |
| // visibility: collapsed children do not participate in our positioning. |
| // But we need to lay them down. |
| child->layoutIfNeeded(); |
| continue; |
| } |
| |
| // 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, layoutScope); |
| |
| // Now do a layout. |
| child->layoutIfNeeded(); |
| |
| // We can place the child now, using our value of box-align. |
| LayoutUnit childX = borderLeft() + paddingLeft(); |
| switch (style()->boxAlign()) { |
| case BCENTER: |
| case BBASELINE: // Baseline just maps to center for vertical boxes |
| childX += child->marginLeft() + |
| ((contentWidth() - |
| (child->size().width() + child->marginWidth())) / |
| 2).clampNegativeToZero(); |
| break; |
| case BEND: |
| if (!style()->isLeftToRightDirection()) |
| childX += child->marginLeft(); |
| else |
| childX += |
| contentWidth() - child->marginRight() - child->size().width(); |
| break; |
| default: // BSTART/BSTRETCH |
| if (style()->isLeftToRightDirection()) |
| childX += child->marginLeft(); |
| else |
| childX += |
| contentWidth() - child->marginRight() - child->size().width(); |
| break; |
| } |
| |
| // Place the child. |
| placeChild(child, LayoutPoint(childX, size().height())); |
| setHeight(size().height() + child->size().height() + |
| child->marginBottom()); |
| |
| if (paginated) |
| updateFragmentationInfoForChild(*child); |
| } |
| |
| yPos = size().height(); |
| |
| if (!iterator.first() && hasLineIfEmpty()) |
| setHeight(size().height() + lineHeight(true, |
| style()->isHorizontalWritingMode() |
| ? HorizontalLine |
| : VerticalLine, |
| PositionOfInteriorLineBoxes)); |
| |
| setHeight(size().height() + toAdd); |
| |
| // 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() < minHeight) |
| setHeight(minHeight); |
| |
| // Now we have to calc our height, so we know how much space we have |
| // remaining. |
| oldHeight = size().height(); |
| updateLogicalHeight(); |
| if (oldHeight != size().height()) |
| heightSpecified = true; |
| |
| remainingSpace = size().height() - borderBottom() - paddingBottom() - |
| horizontalScrollbarHeight() - yPos; |
| |
| if (flexingChildren) { |
| haveFlex = false; // We're done. |
| } else if (haveFlex) { |
| // We have some flexible objects. See if we need to grow/shrink them at |
| // all. |
| if (!remainingSpace) |
| break; |
| |
| // Allocate the remaining space among the flexible objects. If we are |
| // trying to grow, then we go from the lowest flex group to the highest |
| // flex group. For shrinking, we go from the highest flex group to the |
| // lowest group. |
| bool expanding = remainingSpace > 0; |
| unsigned start = expanding ? lowestFlexGroup : highestFlexGroup; |
| unsigned end = expanding ? highestFlexGroup : lowestFlexGroup; |
| for (unsigned i = start; i <= end && remainingSpace; i++) { |
| // Always start off by assuming the group can get all the remaining |
| // space. |
| LayoutUnit groupRemainingSpace = remainingSpace; |
| 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 groupRemainingSpaceAtBeginning = groupRemainingSpace; |
| float totalFlex = 0.0f; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) |
| totalFlex += child->style()->boxFlex(); |
| } |
| LayoutUnit spaceAvailableThisPass = groupRemainingSpace; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| LayoutUnit allowedFlex = allowedChildFlex(child, expanding, i); |
| if (allowedFlex) { |
| LayoutUnit projectedFlex = |
| (allowedFlex == LayoutUnit::max()) |
| ? allowedFlex |
| : static_cast<LayoutUnit>( |
| allowedFlex * |
| (totalFlex / child->style()->boxFlex())); |
| spaceAvailableThisPass = |
| expanding ? std::min(spaceAvailableThisPass, projectedFlex) |
| : std::max(spaceAvailableThisPass, projectedFlex); |
| } |
| } |
| |
| // The flex groups may not have any flexible objects this time around. |
| if (!spaceAvailableThisPass || totalFlex == 0.0f) { |
| // If we just couldn't grow/shrink any more, then it's time to |
| // transition to the next flex group. |
| groupRemainingSpace = LayoutUnit(); |
| continue; |
| } |
| |
| // Now distribute the space to objects. |
| for (LayoutBox* child = iterator.first(); |
| child && spaceAvailableThisPass && totalFlex; |
| child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| LayoutUnit spaceAdd = static_cast<LayoutUnit>( |
| spaceAvailableThisPass * |
| (child->style()->boxFlex() / totalFlex)); |
| if (spaceAdd) { |
| child->setOverrideLogicalContentHeight( |
| contentHeightForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = true; |
| } |
| |
| spaceAvailableThisPass -= spaceAdd; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| |
| totalFlex -= child->style()->boxFlex(); |
| } |
| } |
| if (groupRemainingSpace == groupRemainingSpaceAtBeginning) { |
| // This is not advancing, avoid getting stuck by distributing the |
| // remaining pixels. |
| LayoutUnit spaceAdd = LayoutUnit(groupRemainingSpace > 0 ? 1 : -1); |
| for (LayoutBox* child = iterator.first(); |
| child && groupRemainingSpace; child = iterator.next()) { |
| if (allowedChildFlex(child, expanding, i)) { |
| child->setOverrideLogicalContentHeight( |
| contentHeightForChild(child) + spaceAdd); |
| flexingChildren = true; |
| relayoutChildren = true; |
| remainingSpace -= spaceAdd; |
| groupRemainingSpace -= spaceAdd; |
| } |
| } |
| } |
| } while (absoluteValue(groupRemainingSpace) >= 1); |
| } |
| |
| // We didn't find any children that could grow. |
| if (haveFlex && !flexingChildren) |
| haveFlex = false; |
| } |
| } while (haveFlex); |
| |
| if (style()->boxPack() != BoxPackStart && remainingSpace > 0) { |
| // Children must be repositioned. |
| LayoutUnit offset; |
| if (style()->boxPack() == BoxPackJustify) { |
| // Determine the total number of children. |
| int totalChildren = 0; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| ++totalChildren; |
| } |
| |
| // Iterate over the children and space them out according to the |
| // justification level. |
| if (totalChildren > 1) { |
| --totalChildren; |
| bool firstChild = true; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| if (firstChild) { |
| firstChild = false; |
| continue; |
| } |
| |
| offset += remainingSpace / totalChildren; |
| remainingSpace -= (remainingSpace / totalChildren); |
| --totalChildren; |
| placeChild(child, |
| child->location() + LayoutSize(LayoutUnit(), offset)); |
| } |
| } |
| } else { |
| if (style()->boxPack() == BoxPackCenter) |
| offset += remainingSpace / 2; |
| else // END |
| offset += remainingSpace; |
| for (LayoutBox* child = iterator.first(); child; |
| child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| 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 (heightSpecified) |
| setHeight(oldHeight); |
| } |
| |
| void LayoutDeprecatedFlexibleBox::applyLineClamp(FlexBoxIterator& iterator, |
| bool relayoutChildren) { |
| UseCounter::count(document(), UseCounter::LineClamp); |
| |
| int maxLineCount = 0; |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| child->clearOverrideSize(); |
| if (relayoutChildren || (child->isAtomicInlineLevel() && |
| (child->style()->width().isPercentOrCalc() || |
| child->style()->height().isPercentOrCalc())) || |
| (child->style()->height().isAuto() && child->isLayoutBlock())) { |
| child->setChildNeedsLayout(MarkOnlyThis); |
| |
| // Dirty all the positioned objects. |
| if (child->isLayoutBlockFlow()) { |
| toLayoutBlockFlow(child)->markPositionedObjectsForLayout(); |
| clearTruncation(toLayoutBlockFlow(child)); |
| } |
| } |
| child->layoutIfNeeded(); |
| if (child->style()->height().isAuto() && child->isLayoutBlockFlow()) |
| maxLineCount = |
| std::max(maxLineCount, lineCount(toLayoutBlockFlow(child))); |
| } |
| |
| // 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. |
| LineClampValue lineClamp = style()->lineClamp(); |
| int numVisibleLines = |
| lineClamp.isPercentage() |
| ? std::max(1, (maxLineCount + 1) * lineClamp.value() / 100) |
| : lineClamp.value(); |
| if (numVisibleLines >= maxLineCount) |
| return; |
| |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child) || |
| !child->style()->height().isAuto() || !child->isLayoutBlockFlow()) |
| continue; |
| |
| LayoutBlockFlow* blockChild = toLayoutBlockFlow(child); |
| int lineCount = blink::lineCount(blockChild); |
| if (lineCount <= numVisibleLines) |
| continue; |
| |
| int dummyCount = 0; |
| LayoutUnit newHeight( |
| getHeightForLineCount(blockChild, numVisibleLines, true, dummyCount)); |
| if (newHeight == child->size().height()) |
| continue; |
| |
| child->setOverrideLogicalContentHeight(newHeight - |
| child->borderAndPaddingHeight()); |
| child->forceChildLayout(); |
| |
| // FIXME: For now don't support RTL. |
| if (style()->direction() != TextDirection::Ltr) |
| continue; |
| |
| // Get the last line |
| RootInlineBox* lastLine = lineAtIndex(blockChild, lineCount - 1); |
| if (!lastLine) |
| continue; |
| |
| RootInlineBox* lastVisibleLine = |
| lineAtIndex(blockChild, numVisibleLines - 1); |
| if (!lastVisibleLine) |
| continue; |
| |
| DEFINE_STATIC_LOCAL(AtomicString, ellipsisStr, |
| (&horizontalEllipsisCharacter, 1)); |
| const Font& font = style(numVisibleLines == 1)->font(); |
| float totalWidth = |
| font.width(constructTextRun(font, &horizontalEllipsisCharacter, 1, |
| styleRef(), style()->direction())); |
| |
| // See if this width can be accommodated on the last visible line |
| LineLayoutBlockFlow destBlock = lastVisibleLine->block(); |
| LineLayoutBlockFlow srcBlock = lastLine->block(); |
| |
| // FIXME: Directions of src/destBlock could be different from our direction |
| // and from one another. |
| if (!srcBlock.style()->isLeftToRightDirection()) |
| continue; |
| |
| bool leftToRight = destBlock.style()->isLeftToRightDirection(); |
| if (!leftToRight) |
| continue; |
| |
| LayoutUnit blockRightEdge = destBlock.logicalRightOffsetForLine( |
| lastVisibleLine->y(), DoNotIndentText); |
| if (!lastVisibleLine->lineCanAccommodateEllipsis( |
| leftToRight, blockRightEdge.toInt(), |
| (lastVisibleLine->x() + lastVisibleLine->logicalWidth()).toInt(), |
| totalWidth)) |
| continue; |
| |
| // Let the truncation code kick in. |
| // FIXME: the text alignment should be recomputed after the width changes |
| // due to truncation. |
| LayoutUnit blockLeftEdge = destBlock.logicalLeftOffsetForLine( |
| lastVisibleLine->y(), DoNotIndentText); |
| lastVisibleLine->placeEllipsis(ellipsisStr, leftToRight, blockLeftEdge, |
| blockRightEdge, LayoutUnit(totalWidth)); |
| destBlock.setHasMarkupTruncation(true); |
| } |
| } |
| |
| void LayoutDeprecatedFlexibleBox::clearLineClamp() { |
| FlexBoxIterator iterator(this); |
| for (LayoutBox* child = iterator.first(); child; child = iterator.next()) { |
| if (childDoesNotAffectWidthOrFlexing(child)) |
| continue; |
| |
| child->clearOverrideSize(); |
| if ((child->isAtomicInlineLevel() && |
| (child->style()->width().isPercentOrCalc() || |
| child->style()->height().isPercentOrCalc())) || |
| (child->style()->height().isAuto() && child->isLayoutBlock())) { |
| child->setChildNeedsLayout(); |
| |
| if (child->isLayoutBlockFlow()) { |
| toLayoutBlockFlow(child)->markPositionedObjectsForLayout(); |
| clearTruncation(toLayoutBlockFlow(child)); |
| } |
| } |
| } |
| } |
| |
| void LayoutDeprecatedFlexibleBox::placeChild(LayoutBox* child, |
| const LayoutPoint& location) { |
| // FIXME Investigate if this can be removed based on other flags. |
| // crbug.com/370010 |
| child->setMayNeedPaintInvalidation(); |
| |
| // Place the child. |
| child->setLocation(location); |
| } |
| |
| LayoutUnit LayoutDeprecatedFlexibleBox::allowedChildFlex(LayoutBox* child, |
| bool expanding, |
| unsigned group) { |
| if (childDoesNotAffectWidthOrFlexing(child) || |
| child->style()->boxFlex() == 0.0f || |
| child->style()->boxFlexGroup() != group) |
| return LayoutUnit(); |
| |
| if (expanding) { |
| if (isHorizontal()) { |
| // FIXME: For now just handle fixed values. |
| LayoutUnit maxWidth = LayoutUnit::max(); |
| LayoutUnit width = contentWidthForChild(child); |
| if (child->style()->maxWidth().isFixed()) |
| maxWidth = LayoutUnit(child->style()->maxWidth().value()); |
| if (maxWidth == LayoutUnit::max()) |
| return maxWidth; |
| return (maxWidth - width).clampNegativeToZero(); |
| } |
| // FIXME: For now just handle fixed values. |
| LayoutUnit maxHeight = LayoutUnit::max(); |
| LayoutUnit height = contentHeightForChild(child); |
| if (child->style()->maxHeight().isFixed()) |
| maxHeight = LayoutUnit(child->style()->maxHeight().value()); |
| if (maxHeight == LayoutUnit::max()) |
| return maxHeight; |
| return (maxHeight - height).clampNegativeToZero(); |
| } |
| |
| // FIXME: For now just handle fixed values. |
| if (isHorizontal()) { |
| LayoutUnit minWidth = child->minPreferredLogicalWidth(); |
| LayoutUnit width = contentWidthForChild(child); |
| if (child->style()->minWidth().isFixed()) |
| minWidth = LayoutUnit(child->style()->minWidth().value()); |
| else if (child->style()->minWidth().type() == Auto) |
| minWidth = LayoutUnit(); |
| |
| LayoutUnit allowedShrinkage = (minWidth - width).clampPositiveToZero(); |
| return allowedShrinkage; |
| } |
| Length minHeight = child->style()->minHeight(); |
| if (minHeight.isFixed() || minHeight.isAuto()) { |
| LayoutUnit minHeight(child->style()->minHeight().value()); |
| LayoutUnit height = contentHeightForChild(child); |
| LayoutUnit allowedShrinkage = (minHeight - height).clampPositiveToZero(); |
| return allowedShrinkage; |
| } |
| return LayoutUnit(); |
| } |
| |
| } // namespace blink |