blob: 09ac5911a0a9a7e58e472106b7f2094866f6b5e8 [file] [log] [blame]
/*
* Copyright (C) 1997 Martin Jones (mjones@kde.org)
* (C) 1997 Torben Weis (weis@kde.org)
* (C) 1998 Waldo Bastian (bastian@kde.org)
* (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2009, 2010 Apple 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.
*/
#ifndef LayoutTable_h
#define LayoutTable_h
#include "core/CSSPropertyNames.h"
#include "core/CoreExport.h"
#include "core/layout/LayoutBlock.h"
#include "core/style/CollapsedBorderValue.h"
#include "wtf/Vector.h"
#include <memory>
namespace blink {
class LayoutTableCol;
class LayoutTableCaption;
class LayoutTableCell;
class LayoutTableSection;
class TableLayoutAlgorithm;
enum SkipEmptySectionsValue { DoNotSkipEmptySections, SkipEmptySections };
// LayoutTable is the LayoutObject associated with
// display: table or inline-table.
//
// LayoutTable is the master coordinator for determining the overall table
// structure. The reason is that LayoutTableSection children have a local
// view over what their structure is but don't account for other
// LayoutTableSection. Thus LayoutTable helps keep consistency across
// LayoutTableSection. See e.g. |m_effectiveColumns| below.
//
// LayoutTable expects only 3 types of children:
// - zero or more LayoutTableCol
// - zero or more LayoutTableCaption
// - zero or more LayoutTableSection
// This is aligned with what HTML5 expects:
// https://html.spec.whatwg.org/multipage/tables.html#the-table-element
// with one difference: we allow more than one caption as we follow what
// CSS expects (https://bugs.webkit.org/show_bug.cgi?id=69773).
// Those expectations are enforced by LayoutTable::addChild, that wraps unknown
// children into an anonymous LayoutTableSection. This is what the "generate
// missing child wrapper" step in CSS mandates in
// http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes.
//
// LayoutTable assumes a pretty strict structure that is mandated by CSS:
// (note that this structure in HTML is enforced by the HTML5 Parser).
//
// LayoutTable
// | |
// LayoutTableSection LayoutTableCaption
// |
// LayoutTableRow
// |
// LayoutTableCell
//
// This means that we have to generate some anonymous table wrappers in order to
// satisfy the structure. See again
// http://www.w3.org/TR/CSS21/tables.html#anonymous-boxes.
// The anonymous table wrappers are inserted in LayoutTable::addChild,
// LayoutTableSection::addChild, LayoutTableRow::addChild and
// LayoutObject::addChild.
//
// Note that this yields to interesting issues in the insertion code. The DOM
// code is unaware of the anonymous LayoutObjects and thus can insert
// LayoutObjects into a different part of the layout tree. An example is:
//
// <!DOCTYPE html>
// <style>
// tablerow { display: table-row; }
// tablecell { display: table-cell; border: 5px solid purple; }
// </style>
// <tablerow id="firstRow">
// <tablecell>Short first row.</tablecell>
// </tablerow>
// <tablecell id="cell">Long second row, shows the table structure.</tablecell>
//
// The page generates a single anonymous table (LayoutTable) and table row group
// (LayoutTableSection) to wrap the <tablerow> (#firstRow) and an anonymous
// table row (LayoutTableRow) for the second <tablecell>.
// It is possible for JavaScript to insert a new element between these 2
// <tablecell> (using Node.insertBefore), requiring us to split the anonymous
// table (or the anonymous table row group) in 2. Also note that even
// though the second <tablecell> and <tablerow> are siblings in the DOM tree,
// they are not in the layout tree.
//
//
// Note about absolute column index vs effective column index:
//
// To save memory at the expense of massive code complexity, the code tries
// to coalesce columns. This means that we try to the wider column grouping
// seen over the LayoutTableSections.
//
// Note that this is also a defensive pattern as <td colspan="6666666666">
// only allocates a single entry in this Vector. This argument is weak
// though as we cap colspans in HTMLTableCellElement.
//
// The following example would have 2 entries [ 3, 2 ] in effectiveColumns():
// <table>
// <tr>
// <td colspan="3"></td>
// <td colspan="2"></td>
// </tr>
// </table>
//
// Columns can be split if we add a row with a different colspan structure.
// See splitEffectiveColumn() and appendEffectiveColumn() for operations
// over effectiveColumns() and effectiveColumnPositions().
//
// See absoluteColumnToEffectiveColumn() for converting an absolute column
// index into an index into effectiveColumns() and effectiveColumnPositions().
class CORE_EXPORT LayoutTable final : public LayoutBlock {
public:
explicit LayoutTable(Element*);
~LayoutTable() override;
// Per CSS 3 writing-mode: "The first and second values of the
// 'border-spacing' property represent spacing between columns and rows
// respectively, not necessarily the horizontal and vertical spacing
// respectively".
int hBorderSpacing() const { return m_hSpacing; }
int vBorderSpacing() const { return m_vSpacing; }
bool collapseBorders() const {
return style()->borderCollapse() == EBorderCollapse::Collapse;
}
int borderStart() const override { return m_borderStart; }
int borderEnd() const override { return m_borderEnd; }
int borderBefore() const override;
int borderAfter() const override;
int borderLeft() const override {
if (style()->isHorizontalWritingMode())
return style()->isLeftToRightDirection() ? borderStart() : borderEnd();
return style()->isFlippedBlocksWritingMode() ? borderAfter()
: borderBefore();
}
int borderRight() const override {
if (style()->isHorizontalWritingMode())
return style()->isLeftToRightDirection() ? borderEnd() : borderStart();
return style()->isFlippedBlocksWritingMode() ? borderBefore()
: borderAfter();
}
int borderTop() const override {
if (style()->isHorizontalWritingMode())
return style()->isFlippedBlocksWritingMode() ? borderAfter()
: borderBefore();
return style()->isLeftToRightDirection() ? borderStart() : borderEnd();
}
int borderBottom() const override {
if (style()->isHorizontalWritingMode())
return style()->isFlippedBlocksWritingMode() ? borderBefore()
: borderAfter();
return style()->isLeftToRightDirection() ? borderEnd() : borderStart();
}
int outerBorderBefore() const;
int outerBorderAfter() const;
int outerBorderStart() const;
int outerBorderEnd() const;
int outerBorderLeft() const {
if (style()->isHorizontalWritingMode())
return style()->isLeftToRightDirection() ? outerBorderStart()
: outerBorderEnd();
return style()->isFlippedBlocksWritingMode() ? outerBorderAfter()
: outerBorderBefore();
}
int outerBorderRight() const {
if (style()->isHorizontalWritingMode())
return style()->isLeftToRightDirection() ? outerBorderEnd()
: outerBorderStart();
return style()->isFlippedBlocksWritingMode() ? outerBorderBefore()
: outerBorderAfter();
}
int outerBorderTop() const {
if (style()->isHorizontalWritingMode())
return style()->isFlippedBlocksWritingMode() ? outerBorderAfter()
: outerBorderBefore();
return style()->isLeftToRightDirection() ? outerBorderStart()
: outerBorderEnd();
}
int outerBorderBottom() const {
if (style()->isHorizontalWritingMode())
return style()->isFlippedBlocksWritingMode() ? outerBorderBefore()
: outerBorderAfter();
return style()->isLeftToRightDirection() ? outerBorderEnd()
: outerBorderStart();
}
int calcBorderStart() const;
int calcBorderEnd() const;
void recalcBordersInRowDirection();
void addChild(LayoutObject* child,
LayoutObject* beforeChild = nullptr) override;
struct ColumnStruct {
DISALLOW_NEW_EXCEPT_PLACEMENT_NEW();
explicit ColumnStruct(unsigned initialSpan = 1) : span(initialSpan) {}
unsigned span;
};
void forceSectionsRecalc() {
setNeedsSectionRecalc();
recalcSections();
}
const Vector<ColumnStruct>& effectiveColumns() const {
return m_effectiveColumns;
}
const Vector<int>& effectiveColumnPositions() const {
return m_effectiveColumnPositions;
}
void setEffectiveColumnPosition(unsigned index, int position) {
// Note that if our horizontal border-spacing changed, our position will
// change but not our column's width. In practice, horizontal border-spacing
// won't change often.
m_columnLogicalWidthChanged |=
m_effectiveColumnPositions[index] != position;
m_effectiveColumnPositions[index] = position;
}
LayoutTableSection* header() const { return m_head; }
LayoutTableSection* footer() const { return m_foot; }
LayoutTableSection* firstBody() const { return m_firstBody; }
// This function returns 0 if the table has no section.
LayoutTableSection* topSection() const;
LayoutTableSection* bottomSection() const;
// This function returns 0 if the table has no non-empty sections.
LayoutTableSection* topNonEmptySection() const;
unsigned lastEffectiveColumnIndex() const {
return numEffectiveColumns() - 1;
}
void splitEffectiveColumn(unsigned index, unsigned firstSpan);
void appendEffectiveColumn(unsigned span);
unsigned numEffectiveColumns() const { return m_effectiveColumns.size(); }
unsigned spanOfEffectiveColumn(unsigned effectiveColumnIndex) const {
return m_effectiveColumns[effectiveColumnIndex].span;
}
unsigned absoluteColumnToEffectiveColumn(unsigned absoluteColumnIndex) const {
if (absoluteColumnIndex < m_noCellColspanAtLeast)
return absoluteColumnIndex;
unsigned effectiveColumn = m_noCellColspanAtLeast;
unsigned numColumns = numEffectiveColumns();
for (unsigned c = m_noCellColspanAtLeast;
effectiveColumn < numColumns &&
c + m_effectiveColumns[effectiveColumn].span - 1 < absoluteColumnIndex;
++effectiveColumn)
c += m_effectiveColumns[effectiveColumn].span;
return effectiveColumn;
}
unsigned effectiveColumnToAbsoluteColumn(
unsigned effectiveColumnIndex) const {
if (effectiveColumnIndex < m_noCellColspanAtLeast)
return effectiveColumnIndex;
unsigned c = m_noCellColspanAtLeast;
for (unsigned i = m_noCellColspanAtLeast; i < effectiveColumnIndex; i++)
c += m_effectiveColumns[i].span;
return c;
}
LayoutUnit borderSpacingInRowDirection() const {
if (unsigned effectiveColumnCount = numEffectiveColumns())
return static_cast<LayoutUnit>(effectiveColumnCount + 1) *
hBorderSpacing();
return LayoutUnit();
}
// The collapsing border model dissallows paddings on table, which is why we
// override those functions.
// See http://www.w3.org/TR/CSS2/tables.html#collapsing-borders.
LayoutUnit paddingTop() const override;
LayoutUnit paddingBottom() const override;
LayoutUnit paddingLeft() const override;
LayoutUnit paddingRight() const override;
// Override paddingStart/End to return pixel values to match behavor of
// LayoutTableCell.
LayoutUnit paddingEnd() const override {
return LayoutUnit(LayoutBlock::paddingEnd().toInt());
}
LayoutUnit paddingStart() const override {
return LayoutUnit(LayoutBlock::paddingStart().toInt());
}
LayoutUnit bordersPaddingAndSpacingInRowDirection() const {
// 'border-spacing' only applies to separate borders (see 17.6.1 The
// separated borders model).
return borderStart() + borderEnd() +
(collapseBorders() ? LayoutUnit() : (paddingStart() + paddingEnd() +
borderSpacingInRowDirection()));
}
// Return the first column or column-group.
LayoutTableCol* firstColumn() const;
struct ColAndColGroup {
ColAndColGroup()
: col(nullptr),
colgroup(nullptr),
adjoinsStartBorderOfColGroup(false),
adjoinsEndBorderOfColGroup(false) {}
LayoutTableCol* col;
LayoutTableCol* colgroup;
bool adjoinsStartBorderOfColGroup;
bool adjoinsEndBorderOfColGroup;
LayoutTableCol* innermostColOrColGroup() { return col ? col : colgroup; }
};
ColAndColGroup colElementAtAbsoluteColumn(
unsigned absoluteColumnIndex) const {
// The common case is to not have col/colgroup elements, make that case
// fast.
if (!m_hasColElements)
return ColAndColGroup();
return slowColElementAtAbsoluteColumn(absoluteColumnIndex);
}
bool needsSectionRecalc() const { return m_needsSectionRecalc; }
void setNeedsSectionRecalc() {
if (documentBeingDestroyed())
return;
m_needsSectionRecalc = true;
setNeedsLayoutAndFullPaintInvalidation(
LayoutInvalidationReason::TableChanged);
}
LayoutTableSection* sectionAbove(
const LayoutTableSection*,
SkipEmptySectionsValue = DoNotSkipEmptySections) const;
LayoutTableSection* sectionBelow(
const LayoutTableSection*,
SkipEmptySectionsValue = DoNotSkipEmptySections) const;
LayoutTableCell* cellAbove(const LayoutTableCell*) const;
LayoutTableCell* cellBelow(const LayoutTableCell*) const;
LayoutTableCell* cellBefore(const LayoutTableCell*) const;
LayoutTableCell* cellAfter(const LayoutTableCell*) const;
typedef Vector<CollapsedBorderValue> CollapsedBorderValues;
void invalidateCollapsedBorders();
bool hasSections() const { return m_head || m_foot || m_firstBody; }
void recalcSectionsIfNeeded() const {
if (m_needsSectionRecalc)
recalcSections();
}
static LayoutTable* createAnonymousWithParent(const LayoutObject*);
LayoutBox* createAnonymousBoxWithSameTypeAs(
const LayoutObject* parent) const override {
return createAnonymousWithParent(parent);
}
const BorderValue& tableStartBorderAdjoiningCell(
const LayoutTableCell*) const;
const BorderValue& tableEndBorderAdjoiningCell(const LayoutTableCell*) const;
void addCaption(const LayoutTableCaption*);
void removeCaption(const LayoutTableCaption*);
void addColumn(const LayoutTableCol*);
void removeColumn(const LayoutTableCol*);
void paintBoxDecorationBackground(const PaintInfo&,
const LayoutPoint&) const final;
void paintMask(const PaintInfo&, const LayoutPoint&) const final;
const CollapsedBorderValues& collapsedBorders() const {
ASSERT(m_collapsedBordersValid);
return m_collapsedBorders;
}
void subtractCaptionRect(LayoutRect&) const;
bool isLogicalWidthAuto() const;
// When table headers are repeated, we need to know the offset from the block
// start of the fragmentation context to the first occurrence of the table
// header.
LayoutUnit blockOffsetToFirstRepeatableHeader() const {
return m_blockOffsetToFirstRepeatableHeader;
}
const char* name() const override { return "LayoutTable"; }
// Whether a table has opaque foreground depends on many factors, e.g. border
// spacing, missing cells, etc. For simplicity, just conservatively assume
// foreground of all tables are not opaque.
bool foregroundIsKnownToBeOpaqueInRect(const LayoutRect&,
unsigned) const override {
return false;
}
enum WhatToMarkAllCells { MarkDirtyOnly, MarkDirtyAndNeedsLayout };
void markAllCellsWidthsDirtyAndOrNeedsLayout(WhatToMarkAllCells);
protected:
void styleDidChange(StyleDifference, const ComputedStyle* oldStyle) override;
void simplifiedNormalFlowLayout() override;
bool recalcChildOverflowAfterStyleChange() override;
void ensureIsReadyForPaintInvalidation() override;
PaintInvalidationReason invalidatePaintIfNeeded(
const PaintInvalidationState&) override;
PaintInvalidationReason invalidatePaintIfNeeded(
const PaintInvalidatorContext&) const override;
private:
bool isOfType(LayoutObjectType type) const override {
return type == LayoutObjectTable || LayoutBlock::isOfType(type);
}
void paintObject(const PaintInfo&, const LayoutPoint&) const override;
void layout() override;
void computeIntrinsicLogicalWidths(LayoutUnit& minWidth,
LayoutUnit& maxWidth) const override;
void computePreferredLogicalWidths() override;
bool nodeAtPoint(HitTestResult&,
const HitTestLocation& locationInContainer,
const LayoutPoint& accumulatedOffset,
HitTestAction) override;
int baselinePosition(
FontBaseline,
bool firstLine,
LineDirectionMode,
LinePositionMode = PositionOnContainingLine) const override;
int firstLineBoxBaseline() const override;
int inlineBlockBaseline(LineDirectionMode) const override;
ColAndColGroup slowColElementAtAbsoluteColumn(unsigned col) const;
void updateColumnCache() const;
void invalidateCachedColumns();
void updateLogicalWidth() override;
LayoutUnit convertStyleLogicalWidthToComputedWidth(
const Length& styleLogicalWidth,
LayoutUnit availableWidth) const;
LayoutUnit convertStyleLogicalHeightToComputedHeight(
const Length& styleLogicalHeight) const;
LayoutRect overflowClipRect(
const LayoutPoint& location,
OverlayScrollbarClipBehavior = IgnoreOverlayScrollbarSize) const override;
void addOverflowFromChildren() override;
void recalcSections() const;
void layoutCaption(LayoutTableCaption&, SubtreeLayoutScope&);
void layoutSection(LayoutTableSection&,
SubtreeLayoutScope&,
LayoutUnit logicalLeft);
// Return the logical height based on the height, min-height and max-height
// properties from CSS. Will return 0 if auto.
LayoutUnit logicalHeightFromStyle() const;
void distributeExtraLogicalHeight(int extraLogicalHeight);
void recalcCollapsedBordersIfNeeded();
// TODO(layout-dev): All mutables in this class are lazily updated by
// recalcSections() which is called by various getter methods (e.g.
// borderBefore(), borderAfter()).
// They allow dirty layout even after DocumentLifecycle::LayoutClean which
// seems not proper. crbug.com/538236.
// Holds spans (number of absolute columns) of effective columns.
// See "absolute column index vs effective column index" in comments of
// LayoutTable.
mutable Vector<ColumnStruct> m_effectiveColumns;
// Holds the logical layout positions of effective columns, and the last item
// (whose index is numEffectiveColumns()) holds the position of the imaginary
// column after the last column.
// Because of the last item, m_effectiveColumnPositions.size() is always
// numEffectiveColumns() + 1.
mutable Vector<int> m_effectiveColumnPositions;
// The captions associated with this object.
mutable Vector<LayoutTableCaption*> m_captions;
// Holds pointers to LayoutTableCol objects for <col>s and <colgroup>s under
// this table.
// There is no direct relationship between the size of and index into this
// vector and those of m_effectiveColumns because they hold different things.
mutable Vector<LayoutTableCol*> m_columnLayoutObjects;
mutable LayoutTableSection* m_head;
mutable LayoutTableSection* m_foot;
mutable LayoutTableSection* m_firstBody;
// The layout algorithm used by this table.
//
// CSS 2.1 defines 2 types of table layouts toggled with 'table-layout':
// fixed (TableLayoutAlgorithmFixed) and auto (TableLayoutAlgorithmAuto).
// See http://www.w3.org/TR/CSS21/tables.html#width-layout.
//
// The layout algorithm is delegated to TableLayoutAlgorithm. This enables
// changing 'table-layout' without having to reattach the <table>.
//
// As the algorithm is dependent on the style, this field is nullptr before
// the first style is applied in styleDidChange().
std::unique_ptr<TableLayoutAlgorithm> m_tableLayout;
// A sorted list of all unique border values that we want to paint.
//
// Collapsed borders are SUPER EXPENSIVE to compute. The reason is that we
// need to compare a cells border against all the adjoining cells, rows,
// row groups, column, column groups and table. Thus we cache them in this
// field.
CollapsedBorderValues m_collapsedBorders;
bool m_collapsedBordersValid : 1;
mutable bool m_hasColElements : 1;
mutable bool m_needsSectionRecalc : 1;
bool m_columnLogicalWidthChanged : 1;
mutable bool m_columnLayoutObjectsValid : 1;
mutable unsigned m_noCellColspanAtLeast;
unsigned calcNoCellColspanAtLeast() const {
for (unsigned c = 0; c < numEffectiveColumns(); c++) {
if (m_effectiveColumns[c].span > 1)
return c;
}
return numEffectiveColumns();
}
short m_hSpacing;
short m_vSpacing;
int m_borderStart;
int m_borderEnd;
LayoutUnit m_blockOffsetToFirstRepeatableHeader;
};
inline LayoutTableSection* LayoutTable::topSection() const {
ASSERT(!needsSectionRecalc());
if (m_head)
return m_head;
if (m_firstBody)
return m_firstBody;
return m_foot;
}
DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutTable, isTable());
} // namespace blink
#endif // LayoutTable_h