blob: eae77082fad9c805488403de7df45602d591e422 [file] [log] [blame]
/*
* Copyright (C) 2020 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "TableWrapperBlockFormattingContext.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "BlockFormattingState.h"
#include "DisplayBox.h"
#include "InvalidationState.h"
#include "LayoutChildIterator.h"
#include "LayoutContext.h"
#include "LayoutInitialContainingBlock.h"
#include "TableFormattingContext.h"
#include "TableFormattingState.h"
namespace WebCore {
namespace Layout {
WTF_MAKE_ISO_ALLOCATED_IMPL(TableWrapperBlockFormattingContext);
TableWrapperBlockFormattingContext::TableWrapperBlockFormattingContext(const ContainerBox& formattingContextRoot, BlockFormattingState& formattingState)
: BlockFormattingContext(formattingContextRoot, formattingState)
{
}
void TableWrapperBlockFormattingContext::layoutInFlowContent(InvalidationState&, const ConstraintsForInFlowContent& constraints)
{
// The table generates a principal block container box called the table wrapper box that contains the table box itself and any caption boxes
// (in document order). The table box is a block-level box that contains the table's internal table boxes.
// The caption boxes are principal block-level boxes that retain their own content, padding, margin, and border areas, and are rendered
// as normal block boxes inside the table wrapper box. Whether the caption boxes are placed before or after the table box is decided by
// the 'caption-side' property, as described below.
for (auto& child : childrenOfType<ContainerBox>(root())) {
if (child.isTableBox())
layoutTableBox(child, constraints);
else if (child.isTableCaption())
ASSERT_NOT_IMPLEMENTED_YET();
else
ASSERT_NOT_REACHED();
}
}
void TableWrapperBlockFormattingContext::layoutTableBox(const ContainerBox& tableBox, const ConstraintsForInFlowContent& constraints)
{
layoutState().ensureTableFormattingState(tableBox);
computeBorderAndPaddingForTableBox(tableBox, constraints.horizontal);
computeStaticVerticalPosition(tableBox, constraints.vertical);
computeWidthAndMarginForTableBox(tableBox, constraints.horizontal);
computeStaticHorizontalPosition(tableBox, constraints.horizontal);
if (tableBox.hasChild()) {
auto invalidationState = InvalidationState { };
LayoutContext::createFormattingContext(tableBox, layoutState())->layoutInFlowContent(invalidationState, geometry().constraintsForInFlowContent(tableBox));
}
computeHeightAndMarginForTableBox(tableBox, constraints);
}
void TableWrapperBlockFormattingContext::computeBorderAndPaddingForTableBox(const ContainerBox& tableBox, const HorizontalConstraints& horizontalConstraints)
{
ASSERT(tableBox.isTableBox());
if (!tableBox.hasChild() || tableBox.style().borderCollapse() == BorderCollapse::Separate) {
BlockFormattingContext::computeBorderAndPadding(tableBox, horizontalConstraints);
return;
}
// UAs must compute an initial left and right border width for the table by examining
// the first and last cells in the first row of the table.
// The left border width of the table is half of the first cell's collapsed left border,
// and the right border width of the table is half of the last cell's collapsed right border.
// The top border width of the table is computed by examining all cells who collapse their top
// borders with the top border of the table. The top border width of the table is equal to half of the
// maximum collapsed top border. The bottom border width is computed by examining all cells whose bottom borders collapse
// with the bottom of the table. The bottom border width is equal to half of the maximum collapsed bottom border.
auto& grid = layoutState().establishedTableFormattingState(tableBox).tableGrid();
auto tableBorder = geometry().computedBorder(tableBox);
auto& firstColumnFirstRowBox = grid.slot({ 0 , 0 })->cell().box();
auto leftBorder = std::max(tableBorder.horizontal.left, geometry().computedBorder(firstColumnFirstRowBox).horizontal.left);
auto& lastColumnFirstRow = grid.slot({ grid.columns().size() - 1, 0 })->cell().box();
auto rightBorder = std::max(tableBorder.horizontal.right, geometry().computedBorder(lastColumnFirstRow).horizontal.right);
auto topBorder = tableBorder.vertical.top;
auto bottomBorder = tableBorder.vertical.bottom;
auto lastRowIndex = grid.rows().size() - 1;
for (size_t columnIndex = 0; columnIndex < grid.columns().size(); ++columnIndex) {
auto& boxInFirstRox = grid.slot({ columnIndex, 0 })->cell().box();
auto& boxInLastRow = grid.slot({ columnIndex, lastRowIndex })->cell().box();
topBorder = std::max(topBorder, geometry().computedBorder(boxInFirstRox).vertical.top);
bottomBorder = std::max(bottomBorder, geometry().computedBorder(boxInLastRow).vertical.bottom);
}
topBorder = std::max(topBorder, geometry().computedBorder(*tableBox.firstChild()).vertical.top);
for (auto& section : childrenOfType<ContainerBox>(tableBox)) {
auto horiztonalBorder = geometry().computedBorder(section).horizontal;
leftBorder = std::max(leftBorder, horiztonalBorder.left);
rightBorder = std::max(rightBorder, horiztonalBorder.right);
}
bottomBorder = std::max(bottomBorder, geometry().computedBorder(*tableBox.lastChild()).vertical.bottom);
auto& rows = grid.rows().list();
topBorder = std::max(topBorder, geometry().computedBorder(rows.first().box()).vertical.top);
for (auto& row : rows) {
auto horiztonalBorder = geometry().computedBorder(row.box()).horizontal;
leftBorder = std::max(leftBorder, horiztonalBorder.left);
rightBorder = std::max(rightBorder, horiztonalBorder.right);
}
bottomBorder = std::max(bottomBorder, geometry().computedBorder(rows.last().box()).vertical.bottom);
auto collapsedBorder = Edges { { leftBorder, rightBorder }, { topBorder, bottomBorder } };
grid.setCollapsedBorder(collapsedBorder);
auto& displayBox = formattingState().displayBox(tableBox);
displayBox.setBorder(collapsedBorder / 2);
displayBox.setPadding(geometry().computedPadding(tableBox, horizontalConstraints.logicalWidth));
}
void TableWrapperBlockFormattingContext::computeWidthAndMarginForTableBox(const ContainerBox& tableBox, const HorizontalConstraints& horizontalConstraints)
{
ASSERT(tableBox.isTableBox());
// This is a special table "fit-content size" behavior handling. Not in the spec though.
// Table returns its final width as min/max. Use this final width value to computed horizontal margins etc.
auto& formattingStateForTableBox = layoutState().establishedTableFormattingState(tableBox);
auto intrinsicWidthConstraints = IntrinsicWidthConstraints { };
if (auto precomputedIntrinsicWidthConstraints = formattingStateForTableBox.intrinsicWidthConstraints())
intrinsicWidthConstraints = *precomputedIntrinsicWidthConstraints;
else {
if (tableBox.hasChild())
intrinsicWidthConstraints = LayoutContext::createFormattingContext(tableBox, layoutState())->computedIntrinsicWidthConstraints();
formattingStateForTableBox.setIntrinsicWidthConstraints(intrinsicWidthConstraints);
}
auto availableHorizontalSpace = horizontalConstraints.logicalWidth;
// 1. The table generates a principal block container box called the table wrapper box that contains the table box itself and any caption boxes.
// 2. The table wrapper box establishes a block formatting context, and the table box establishes a table formatting context.
// 3. The computed values of properties 'position', 'float', 'margin-*', 'top', 'right', 'bottom', and 'left' on
// the table element are used on the table wrapper box and not the table box; all other values of non-inheritable
// properties are used on the table box and not the table wrapper box.
// 4. In a block formatting context, each box's left outer edge touches the left edge of the containing block.
// This is true even in the presence of floats, unless the box establishes a new block formatting context (in which case the
// box itself may become narrower due to the floats)
//
// Now consider the following case:
// <div style="display: block; width: 500px;">
// <div style="float: left; width: 100px;"></div>
// <div style="display: table; width: 10%;"></div>
// </div>
// </div>
// 1. We create a table wrapper box to wrap the "display: table" block level box (#1).
// 2. The table wrapper box's width property is set to auto (#3).
// 3. Since it establishes a new block formatting context, the available horizontal space gets shrunk by the float (#4)
// 4. The table wrapper box's used width computes to 500px - 100px -> 400px;
//
// Now we are inside the BFC established by the table wrapper box and try to resolve the table's width -> %10.
// According to the normal BFC rules, it should compute to 10% of the containing block's logical width: 400px -> 40px.
// However in practice it computes to 50px (10% of 500px).
// Similar setup with non-table content would resolve the inner block level box's width to 40px;
// This needs clarification in the spec.
auto horizontalConstraintForResolvingWidth = m_horizontalConstraintsIgnoringFloats.logicalWidth;
auto geometry = this->geometry();
auto computedWidth = geometry.computedWidth(tableBox, horizontalConstraintForResolvingWidth);
auto computedMaxWidth = geometry.computedMaxWidth(tableBox, horizontalConstraintForResolvingWidth);
auto computedMinWidth = geometry.computedMinWidth(tableBox, horizontalConstraintForResolvingWidth);
// Use the generic shrink-to-fit-width logic as the initial width for the table.
auto usedWidth = std::min(std::max(intrinsicWidthConstraints.minimum, availableHorizontalSpace), intrinsicWidthConstraints.maximum);
if (computedWidth || computedMinWidth || computedMaxWidth) {
if (computedWidth) {
// Normalize the computed width value first.
if (computedMaxWidth && *computedWidth > *computedMaxWidth)
computedWidth = computedMaxWidth;
if (computedMinWidth && *computedWidth < *computedMinWidth)
computedWidth = computedMinWidth;
usedWidth = *computedWidth < intrinsicWidthConstraints.minimum ? intrinsicWidthConstraints.minimum : *computedWidth;
}
if (computedMaxWidth && *computedMaxWidth < usedWidth)
usedWidth = intrinsicWidthConstraints.minimum;
if (computedMinWidth && *computedMinWidth > usedWidth)
usedWidth = *computedMinWidth;
}
auto contentWidthAndMargin = geometry.inFlowWidthAndMargin(tableBox, horizontalConstraints, OverrideHorizontalValues { usedWidth, { } });
auto& displayBox = formattingState().displayBox(tableBox);
displayBox.setContentBoxWidth(contentWidthAndMargin.contentWidth);
displayBox.setHorizontalMargin(contentWidthAndMargin.usedMargin);
displayBox.setHorizontalComputedMargin(contentWidthAndMargin.computedMargin);
}
void TableWrapperBlockFormattingContext::computeHeightAndMarginForTableBox(const ContainerBox& tableBox, const ConstraintsForInFlowContent& constraints)
{
ASSERT(tableBox.isTableBox());
// Table is a special BFC content. Its height is mainly driven by the content. Computed height, min-height and max-height are all
// already been taken into account during the TFC layout.
auto heightAndMargin = geometry().inFlowHeightAndMargin(tableBox, constraints.horizontal, { quirks().overrideTableHeight(tableBox) });
auto marginCollapse = this->marginCollapse();
auto collapsedAndPositiveNegativeValues = marginCollapse.collapsedVerticalValues(tableBox, heightAndMargin.nonCollapsedMargin);
// Cache the computed positive and negative margin value pair.
formattingState().setPositiveAndNegativeVerticalMargin(tableBox, collapsedAndPositiveNegativeValues.positiveAndNegativeVerticalValues);
auto verticalMargin = UsedVerticalMargin { heightAndMargin.nonCollapsedMargin, collapsedAndPositiveNegativeValues.collapsedValues };
auto& displayBox = formattingState().displayBox(tableBox);
displayBox.setTop(verticalPositionWithMargin(tableBox, verticalMargin, constraints.vertical));
displayBox.setContentBoxHeight(heightAndMargin.contentHeight);
displayBox.setVerticalMargin(verticalMargin);
// Adjust the previous sibling's margin bottom now that this box's vertical margin is computed.
MarginCollapse::updateMarginAfterForPreviousSibling(*this, marginCollapse, tableBox);
}
}
}
#endif